TypeScript
1.可兼容JavaScript;相比js,加入了注释;添加一个完整的类结构,更好的面向对象;
2.Mac OS X环境搭建:
2.1.安装homebrew(套件管理器)官网:brew.sh:
ruby -e "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install)"
2.2安装npm(nodejs包管理器):
brew install node
2.3.安装typescript(安装成功后,可以键入tsc测试):
npm install -g typescript
tsc
3.IDE中新建typescript文件,添加watcher实现自动编译
也可以用命令的方式编译一个ts文件为js文件:终端目录下,tsc xx.ts,会生成同名js文件
4.TypeScript 7种 基础数据类型:
Boolean:
var isBone: boolean = false; //使用时要给个初始化的值
Number:
var height: number = 6;
String:
var name: string = "bob";
Array:
var list: number[] = [1, 2, 3];
// 或者
var list: Array<string> = [ "joe", ”kevin” ];
获取数组中数据:
List[0];
Enum:
枚举类型:有一定范围的数据可以用枚举类型最好
enum Color {Red, Green, Blue};
var colorName:string = Color[1];
console...
枚举类型还可以进行(数组)下标赋值操作:
enum Color {Red = 10, Green = 12, Blue = 13};
var colorName: string = Color[12]; // Green
// 获取下标:
var c:Color = Color.Green; // 1
Any:
任意类型:可以多次赋值,后面的会覆盖前面的。
var notSure: any = 10;
notSure = "hello";
也可以指定类型,如下指定为数组
Var list: any[ ] = [1, “hello”, false];
List[2] // false
Void:
对函数进行声明:
function tell( ):string{
return "hello";
}
不需要任何返回值,就可以把函数声明为void
function tell1( ):void{}
类:
创建一个类:
class Preson{
// 属性
name:string;
age:number;
// 构造方法
constructor(name:string, age:number){
this.name = name;
this.age = age;
}
// 普通方法
print(){
return this.name + " : " + this.age;
}
}
调用这个类:
var p = new Person(“mike”, 10);
p.print();
类的继承
无构造函数的类的继承:
class Person{
name: string;
age: number;
tell(){
return this.name + ":" + this.age;
}
}
class Student extends Person{
school: string;
tell(){
return this.name + ":" + this.age + ":" + this.school;
}
}
var s = new Student("捷克学院");
// 没有构造函数,无法使用参数,就用如下方式传入参数值
s.name = "jack";
s.age = 23;
s.school = "捷克学院";
// 执行
s.tell();
有构造函数的类的继承:
class Person{
name: string;
age: number;
constructor(name:string, age:number){
this.name = name;
this.age = age;
}
tell(){
return this.name + ":" + this.age;
}
}
class Student extends Person{
school: string;
constructor(school:string){
// 修改父类的参数,super要写在最前面
super("ime", 23);
this.school = school;
}
tell(){
return this.name + ":" + this.age + ":" + this.school;
}
}
var s = new Student("捷克学院");
// s.name = "jack";
// s.age = 23;
// s.school = "捷克学院";
s.tell();
访问修饰符
公有:public(默认)
私有:private
class Person{
public name: string;
/**
* 默认就是public,如果我在这里写private,
* 或者在下面constructor参数中这样写:private name:string,
* 那么继承自它的 Student 就无法访问到 name 这个属性了
**/
age: number;
constructor(name:string, age:number){
this.name = name;
this.age = age;
}
tell(){
return this.name + ":" + this.age;
}
}
class Student extends Person{
school: string;
constructor(school:string){
this.school = school;
super("ime", 23);
}
tell(){
return this.name + ":" + this.age + ":" + this.school;
}
}
var s = new Student("捷克学院");
// s.name = "jack";
// s.age = 23;
// s.school = "捷克学院";
s.tell();
封装的实现
· 利用private实现的私有属性,可以在类中通过getter和setter来对外开发(私有属性或方法)接口
· 在调用方法时,getter和setter会默认执行
class Hello{
private _age:number;
show(){
return this._age;
}
get age():number{
// 可对外开发私有属性
return this._age;
}
set age(newage:number){
// 可修改私有属性
if(newage >200 || newage < 0){
alert("请输入正确的年龄");
}else{
this._age = newage;
}
}
}
var h = new Hello();
h.age = 300;
alert(h.tell());
static(静态)和其使用技巧
类的属性或方法(非静态),可以通过实例化对象调用,如下:
class Person{
name:string;
tell(){
alert("姓名:" + this.name);
}
}
var p = new Person();
p.name = "hello";
p.tell();
但一旦属性或方法声明为Static后,就变为了静态属性 / 静态方法
不能再通过实例化对象调用,必须通过类本身调用:
class Person{
static name:string;
tell(){
alert("姓名:" + Person.name);
}
}
var p = new Person();
Person.name = "hello";
p.tell();
static静态的使用(进阶)技巧:引用数据类型
class Greeter{
greeting:string;
constructor(msg:string){
this.greeting = msg;
}
greet(){
return "Hello," + this.greeting;
}
}
/**
创建一个类型,指定它的类型为当前类的类型;
其实,创建了一个类,也就是创建了一个声明,
可以把当前数据的类型,声明为当前类的类型
我们称它为“引用数据类型”
**/
var green:Greeter;
green = new Greeter("mike");
alert(green.greet());
函数:函数类型
函数有 命名函数 和 匿名函数,在Ts中,可以指定函数参数的类型和函数本身(返回值)的类型:
// 命名函数
function add(x:number, y:number):string{
// 如果返回值的类型不是函数指定的类型,就会报错
// 例如:return x+y;就会报错
return "hello ts";// 类型相符,不会报错
}
// 匿名函数(在js中,上面的命名函数是这种方式的语法糖)
var myadd = function(x:number, y:string):string{
return "hello ts";
}
上面的x,y,并不清楚其意义,我们可以让参数更明确其意义:
/**
* "=>" 前面可以声明参数的语义化名称和类型
* "=>" 后面紧跟着函数类型
**/
var myAddts:(name:string, age:number) => number = function(n, a){
return a;
}
函数:可选和默认参数
可选参数:用 ?表示,可传可不传
function buildName(firstName:string, lastName?:string){
if(lastName){
return fristName + " " + lastName;
} else {
return firstName;
}
}
// 均不会报错
var result1 = buildName("james", "bonde");
var result2 = buildName("bonde");
// 超出最大的参数个数,则会报错
// var result3 = buildName("james", "bonde", "valin");
默认参数:直接在参数上赋值
function buildName(firstName:string, lastName="bonde"){
return firstName + " and " + lastName;
}
// 可以只传一个参数,也可以传两个参数,传两个,则会覆盖默认参数值
var result1 = buildName("Hello: ");
var result2 = buildName("james", "wade");
// 超出最大的参数个数,仍旧会报错
// var result3 = buildName("james", "bonde", "valin");
函数:可变参数
利用es6的扩展运算符(展开运算符)
function peopleName(firstName:string, ...restOfName:string[]){
// restOfName:string[] 代表数据类型为字符串的数组
return firstName + " " + restOfName.join(" ");
}
// 利用可变参数,可以传递无数个参数,在需要传入不确定参数个数时,很好用
var pn = peolpleName("jack", "mary", "honly", "kevin"...);
Lambads 和 this 关键字的使用
var people = {
name:["iwen","ime","if","bean"];
getName:function(){
return function(){
var i = Math.floor(Math.random()*4);
return {
n:this.name[i]
}
}
}
}
var myName = people.getName();
alert("名字:" + myName().n); // 这是访问不到的
此时这个this指向的不是people。调用发现getName中的this关键字指向的是getName,访问不到外部的name属性。
可以用lambads表达式"()=>"修改,箭头函数能保存函数创建时的 this值,而不是调用时的值。
var people = {
name:["iwen","ime","if","bean"];
getName:function(){
return () => {
var i = Math.floor(Math.random()*4);
return {
// 这里的this代表函数创建时的this,因此可以访问到name属性
n:this.name[i]
}
}
}
}
var myName = people.getName();
alert("名字:" + myName().n); // 此时可访问
TypeScript 的重载
function attr(name:string):string;
fucntion attr(age:number):number;
function attr(nameOrAge:any):any{
if(nameOrAge && typeof nameOrAge === "string"){
alert("姓名");
}else{
alert("年龄");
}
}
/**
* 在某些编程语言中,函数重载或方法重载是指能够创建具有不同实现的同名方法。
* 对重载函数的调用将运行与调用上下文相适应的该函数的特定实现,
* 允许一个函数调用根据上下文执行不同的任务。
* 这里如果有不清楚可以看一下这里:https://www.zhihu.com/question/63751258
**/
attr("Hello");
attr(19);
接口-创建接口
// 规范了参数的类型
function printeLabel(labelObj:{label:string}){
console.log(labelObj.label);
}
var myObj = {label:"Hello"};
// var myObj = {label:10};
printeLabel(myObj);
接口-可选属性
interface USB{
name:string;
age:number;
}
function printUSB(pu:USB){
console.log(pu.name);
}
// 如果不定义参数可选,my必须包含两个参数,即string类型的name和number类型的age,否则编译会报错
var my = {name: "ime", age: 100};
printUSB(my);
可选参数通过 " ?"定义
interface USB{
name?:string;
age?:number;
}
function printUSB(pu:USB){
console.log(pu.name);
}
// 定义了参数可选,my就可以按照可选参数传递参数了。
var my = {name: "ime"};
printUSB(my);
接口-函数类型
用接口的类型 来 规定函数的类型
interface SearchFunc{
(source:string, subString:string):boolean;
}
// 定义函数的类型为接口的类型
var mySearch:SearchFunc:
mySearch = function(src:string, sub:string){
var result = src.search(sub);
if(result != -1){
return true;
}else{
return false;
}
}
接口-数组类型
使用接口对数组进行规范化
interface StringArray{
/**
* 数组中index对应的是一个number类型,但数组最终返回的是一个string类型
* 可理解为下标是number类型;数组中的数据是字符串类型
**/
[index:number]:string;
}
var myArray:StringArray;
// 规定了数组中必须是string类型,其他类型都不可以,即使为空,也必须是空字符串。
myArray = ["10", "12"];
alert(myArray[1]);
接口-class类型
interface ClockInterface{
currentTime:Date;
setTime(d:Date);
}
class Clock implements ClockInterface{
currentTime:Date;
setTime(d:Date){
this.currentTime = d;
}
constructor(h:number; m:number){
}
}
接口 - 接口继承与混合类型
接口的继承:
interface Shape{
color:string;
}
interface PenStroke{
penWidth:number;
}
interface Square extends Shape,PenStroke{
sideLength:number;
}
var s = <Square>{}; // 将一个接口初始化在一个变量中
s.color = "blue";
s.penWidth = 10;
s.sideLength = 10;
接口的混合类型:
interface Counter{
interval:number;
reset():void;
(start:number):string;
}
var c:Counter;
c(10);
c.reset();
...
认识泛型
可以让参数类型在使用时才指定类型,而不需要在方法定义时就指定,这样使用更加灵活;它相比于any类型,在使用方法时有更好的明确性。
function Hello(num:number):number{
return num; // 必须是number类型
}
function hello(str:any):any{
return str; // 可以是任意类型
}
// 泛型一般用<T>表示,也可以用其他的大写字母
function hello<T>(arg:T):T{
return arg; // 在调用之前可以是任意类型
}
// 调用时指定参数类型后,就必须是这种类型
var output = hello<string>("world");
泛型的使用
Vue
1、谈谈你对MVVM开发模式的理解
Model:代表数据模型,数据和业务逻辑都在Model层中定义;
View:代表UI视图,负责数据的展示;
ViewModel:负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
这种模式实现了Model和View的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作dom。
2、Vue 有哪些指令?
v-html、v-text、v-show、v-if、v-for等
3、v-if 和 v-show 的区别
v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
4、简述Vue的响应式原理
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
用ES5实现:
// 第一步 实现基本架构
// 第二步 把模型的数据 显示到视图
// 第三步 更新视图同步到模型,再更新视图
// 发布者
class Vue{
constructor(options){
this.options = options;
this.$data = options.data;
this.$el = document.querySelector(options,el);
this._directives = {
// 存放订阅者集合的数组对象
}
this.Observer(this.$data);
this.Compile(this.$el);
}
// 劫持数据
Observer(data){
// 更新视图 局部更新
for(let key in data){
this._driectives[key] = []; // 空类数组对象
let val = data[key]; // 当前的值
let _this = this;
Object.defineProperty(this.$data, key, {
get: function(){
return val;
},
set: function(newVal){
if(newVal !== val){
val = newVal;
//_this._directives[key] 得到myText的数组
_this._directives[key].forEach(watch=>{
// 遍历订阅者的实例
watch.update(); // 更新视图
})
}
}
});
}
}
// 解析指令
Compile(el){
let nodes = el.children;] // 获取APP下面的子元素
for(let i = 0;i < nodes.length; i++){
let node = nodes[i]; // 当前元素
if(node.children.length){
// 如果当前元素含有子元素,就递归调用自己
this.Compile(node);
}
if(node.hasAttribute('v-text')){
// 获取属性值,对号加入对应的订阅者数组
let attrVal = node.getAttribute("v-text")
this._directives[attrVal].push(new Watcher(node, attrVal, this, 'innerHTML'));
}
if(node.hasAttribute('v-model')){
let attrVal = node.getAttribute("v-model");
this._directives[attrVal].push(new Watcher(node, attrVal, this, 'value'));
// 监听文本框事件
node.addEventListener('input', (function(){
return function(){
// console.log(node.value);
// 更新视图到模型
this.$data[attrVal] = node.value;
}
})());
}
}
}
class Watcher{
constructor(el, vm, mySelf, attr){
this.el = el;
this.el = vm;
this.el = mySelf;
this.el = attr;
this.update(); // 初始化数据
}
update(){
// div对象[innerHTML] = vue对象.data['myText']
// input对象[value] = vue对象.data['myText']
this.el[this.attr] = this.mySelf.$data[this.vm];
}
}
}
在Vue3.0版本中式采用ES6的Proxy对象来实现。
// 获取段落节点
const paragraph = document.getElementById('pararaph');
// 获取输入框节点
const input = document.getElementById('input');
// 需要代理的数据对象
const data = {
text: 'hello world'
}
const handler = {
// 监控data中的text属性变化
set: function(target, prop, value){
if(prop === 'text'){
// 更新值
target[prop] = value;
// 更新试图
paragraph.innerHTML = value;
input.value = value;
retur true;
} else {
return false;
}
}
}
// 构造 proxy 对象
const myText = new Proxy(data, handler);
// 添加input 监听事件
input.addEventListener('input', function(e){
myText.text = e.target.value; // 更新myText的值
});
初始化值
myText.text = data.text;
5、Vue中如何在组件内部实现一个双向数据绑定?
假设有一个输入框组件,用户输入时,同步父组件页面中的数据。
具体思路:父组件通过props传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下:
import Vue from 'vue'
const component = {
props: ['value'],
template: ` < div > <input type = "text"@input = "handleInput": value = "value" > </div>`,
data(){
return{}
},
methods:{
handleInput(e){
this.$emit('input',e.target.value)
}
}
}
new Vue({
components:{CompOne:component},
el:'#root',template:`<div><comp-one:value1="value"@input="value = arguments[0]"></comp - one > </div>`,
data(){
return{value:'123'}
}
})
6、Vue中v-model的实现原理是怎样的?
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新
7、Vue中如何监控某个属性值的变化?
比如现在需要监控data中, obj.a 的变化。Vue中监控对象属性的变化你可以这样:
watch: {
obj: {
handler(newValue, oldValue) {
console.log('obj changed')
},
deep: true
}
}
deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:
watch: {
'obj.a': {
handler(newName, oldName) {
console.log('obj.a changed')
}
}
}
还有一种方法,可以通过computed 来实现,只需要:
// 利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。
computed: {
a1() {
return this.obj.a
}
}
8、Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?
<template>
<div>
<ul>
<li v-for="value in obj" :key="value">
{{value}}
</li>
</ul>
<button @click="addObjB">
添加obj.b
</button>
</div>
</template>
<script>
export default {
data() {
return {
obj:
{
a: 'obj.a'
}
}
},
methods: {
addObjB() {
this.obj.b = 'obj.b'console.log(this.obj)
}
}
}
</script>
<style>
</style>
点击button会发现, obj.b 已经成功添加,但是视图并未刷新:
原因在于在Vue实例创建时, obj.b 并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api—— $set():
// $set() 方法相当于手动的去把 obj.b 处理成一个响应式的属性,此时视图也会跟着改变了:
addObjB() {
this.$set(this.obj, 'b', 'obj.b')
console.log(this.obj)
}
视图改变:
9、delete和Vue.delete删除数组的区别
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete / this.$delete 直接删除了数组 改变了数组的键值。
var a = [1, 2, 3, 4]
var b = [1, 2, 3, 4]
delete a[1]
console.log(a)
this.$delete(b, 1)
console.log(b)
10、如何优化SPA应用的首屏加载速度慢的问题?
· 将公用的JS库通过script标签外部引入,减小 app.bundel 的大小,让浏览器并行下载资源文件,提高下载速度;
· 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
· 加一个首屏loading图,提升用户体验;
11、前端如何优化网站性能?
1、减少 HTTP 请求数量
- css sprites
- 合并css和js文件,压缩
- 采用lazyLoad懒加载
2、控制资源文件加载优先级
3、利用浏览器缓存
4、减少重排(Reflow)
- 如果需要在DOM操作时添加样式,尽量使用增加class属性,而不是通过style操作样式。
5、减少 DOM 操作
6、图标使用 IconFont 替换
12、网页从输入网址到渲染完成经历了哪些过程?
- 输入网址;
- 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
- 与web服务器建立TCP连接;
- 浏览器向web服务器发送http请求;
- web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
- 浏览器下载web服务器返回的数据及解析html源文件;
- 生成DOM树,解析css和js,渲染页面,直至显示完成;
Vue的生命周期
beforeCreate(创建前),在数据观测和初始化事件还未开始
created(创建后),完成数据观测,属性和方法的运算,初始化事件, $el 属性还没有显示出来
beforeMount(载入前),在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
mounted(载入后),在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前),在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后),在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前),在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后),在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
什么是vue生命周期?
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
2、vue生命周期的作用是什么?
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
3、vue生命周期总共有几个阶段?
它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。
4、第一次页面加载会触发哪几个钩子?
beforeCreate、created、beforeMount、mounted 。
5、DOM 渲染在哪个周期中就已经完成?
mounted
Vue实现数据双向绑定的原理: Object.defineProperty()
1、首先Object.defineProperty()的用法:
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:Object.defineProperty(obj, prop, descriptor)
obj:要在其上定义属性的对象。
prop:要定义或修改的属性的名称。
descriptror:将被定义或修改的属性描述符。
2、vue实现数据双向绑定主要是:
采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
js通过Object.defineProperty()实现简单的双向绑定:
<body>
<div id="app">
<input type="text" id="txt">
<p id="show">
</p>
</div>
</body>
<script type="text/javascript">
var obj = {};
Object.defineProperty(obj, 'txt', {
get: function (){
return obj
},
set: function (newValue) {
document.getElementById('txt').value = newValue;
document.getElementById('show').innerHTML = newValue
}
}) ;
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value;
});
</script>
js通过Observer、Compile、Watcher实现一个原生JS的双向绑定:
// 第一步 实现基本架构
// 第二步 把模型的数据 显示到视图
// 第三步 更新视图同步到模型,再更新视图
// 发布者
class Vue{
constructor(options){
this.options = options;
this.$data = options.data;
this.$el = document.querySelector(options,el);
this._directives = {
// 存放订阅者集合的数组对象
}
this.Observer(this.$data);
this.Compile(this.$el);
}
// 劫持数据
Observer(data){
// 更新视图 局部更新
for(let key in data){
this._driectives[key] = []; // 空类数组对象
let val = data[key]; // 当前的值
let _this = this;
Object.defineProperty(this.$data, key, {
get: function(){
return val;
},
set: function(newVal){
if(newVal !== val){
val = newVal;
//_this._directives[key] 得到myText的数组
_this._directives[key].forEach(watch=>{
// 遍历订阅者的实例
watch.update(); // 更新视图
})
}
}
});
}
}
// 解析指令
Compile(el){
let nodes = el.children;] // 获取APP下面的子元素
for(let i = 0;i < nodes.length; i++){
let node = nodes[i]; // 当前元素
if(node.children.length){
// 如果当前元素含有子元素,就递归调用自己
this.Compile(node);
}
if(node.hasAttribute('v-text')){
// 获取属性值,对号加入对应的订阅者数组
let attrVal = node.getAttribute("v-text")
this._directives[attrVal].push(new Watcher(node, attrVal, this, 'innerHTML'));
}
if(node.hasAttribute('v-model')){
let attrVal = node.getAttribute("v-model");
this._directives[attrVal].push(new Watcher(node, attrVal, this, 'value'));
// 监听文本框事件
node.addEventListener('input', (function(){
return function(){
// console.log(node.value);
// 更新视图到模型
this.$data[attrVal] = node.value;
}
})());
}
}
}
class Watcher{
constructor(el, vm, mySelf, attr){
this.el = el;
this.vm= vm;
this.mySelf= mySelf;
this.attr= attr;
this.update(); // 初始化数据
}
update(){
// div对象[innerHTML] = vue对象.data['myText']
// input对象[value] = vue对象.data['myText']
this.el[this.attr] = this.mySelf.$data[this.vm];
}
}
}
Vue组件间的参数传递
1、父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件: $emit 方法传递参数
2、非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道)。
Vue的路由实现:hash模式 和 history模式
hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
history模式:history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
Vue与Angular以及React的区别?
1、与AngularJS的区别
相同点:都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。
不同点:AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
2、与React的区别
相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。
vue路由的钩子函数
首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。
beforeEach主要有3个参数to,from,next。
to:route即将进入的目标路由对象。
from:route当前导航正要离开的路由。
next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。
vuex是什么?怎么使用?哪种功能场景使用它?
只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。
在main.js引入store,注入。新建了一个目录store,… export 。
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据。
action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count ++
}
},
actions: {
increment (context) {
context.commit('increment');
}
}
})
modules
项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleC = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB,
c: moduleC
}
})
其他知识点:
1、css只在当前组件起作用
答:在style标签中写入scoped即可 例如: <stylescoped></style>
2、v-if 和 v-show 区别
答:v-if按照条件是否渲染,v-show是display的block或none;
3、router的区别
router是“路由实例”对象包括了路由的跳转方法,钩子函数等。
http 和 https
HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议:
它是一个安全通信通道,它基于HTTP开发,用于在客户计算机和服务器之间交换信息,它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版。它是由Netscape开发并内置于其浏览器中,用于对数据进行压缩和解压操作,并返回网络上传送回的结果。
HTTPS实际上应用了Netscape的安全套接字层(SSL)作为HTTP应用层的子层。(HTTPS使用端口443,而不是象HTTP那样使用端口80来和TCP/IP进行通信。)SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。
HTTPS和SSL支持使用X.509数字认证,如果需要的话用户可以确认发送者是谁。总的来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议要比http协议安全。
在URL前加https://前缀表明是用SSL(安全套接字)加密的,你的电脑与服务器之间收发的信息传输将更加安全。 Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。
HTTPS和HTTP的区别:
https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的。
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全。
详细参看:https://www.jianshu.com/p/6db0c6dc97a9
GET 和 POST
GET 和 POST 报文上的区别
先下结论,GET 和 POST 方法没有实质区别,只是报文格式不同。
GET 和 POST 只是 HTTP 协议中两种请求方式,而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。
报文格式上,不带参数时,最大区别就是第一行方法名不同
POST方法请求报文第一行是这样的
POST /uri HTTP/1.1 \r\n
GET方法请求报文第一行是这样的
GET /uri HTTP/1.1 \r\n
是的,不带参数时他们的区别就仅仅是报文的前几个字符不同而已
带参数时报文的区别呢? 在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中
举个例子,如果参数是 name=chengqm, age=22。
GET 方法简约版报文是这样的
GET /index.php?name=qiming.c&age=22 HTTP/1.1
Host: localhost
POST 方法简约版报文是这样的
POST /index.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
name=qiming.c&age=22
现在我们知道了两种方法本质上是 TCP 连接,没有差别,也就是说,如果我不按规范来也是可以的。我们可以在 URL 上写参数,然后方法使用 POST;也可以在 Body 写参数,然后方法使用 GET。当然,这需要服务端支持。
常见问题:
GET 方法参数写法是固定的吗?
在约定中,我们的参数是写在 ? 后面,用 & 分割。
我们知道,解析报文的过程是通过获取 TCP 数据,用正则等工具从数据中获取 Header 和 Body,从而提取参数。
也就是说,我们可以自己约定参数的写法,只要服务端能够解释出来就行,一种比较流行的写法是 http://www.example.com/user/name/chengqm/age/22。POST 方法比 GET 方法安全?
按照网上大部分文章的解释,POST 比 GET 安全,因为数据在地址栏上不可见。
然而,从传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。
要想安全传输,就只有加密,也就是 HTTPS。GET 方法的长度限制是怎么回事?
HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因。
浏览器原因就不说了,服务器是因为处理长 URL 要消耗比较多的资源,为了性能和安全(防止恶意构造长 URL 来攻击)考虑,会给 URL 长度加限制。POST 方法会产生两个TCP数据包?
HTTP 协议中没有明确说明 POST 会产生两个 TCP 数据包,而且实际测试(Chrome)发现,header 和 body 不会分开发送。
所以,header 和 body 分开发送是部分浏览器或框架的请求方法,不属于 post 必然行为。w3school 里面说 URL 的最大长度是 2048 个字符
url 长度限制是某些浏览器和服务器的限制,和 HTTP 协议没有关系。
跨域 —— 9种跨域方式实现原理
参考:https://mp.weixin.qq.com/s/nkDkofBidKQFp5oG4-aJNg
什么是跨域?
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
同源策略
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。
同源策略限制内容有:
Cookie、LocalStorage、IndexedDB 等存储性内容
DOM 节点
AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
跨域解决方案:
(1)JSONP
(2)cors(后台设置:简单请求和复杂请求)
(3)postMeassage
HTML5 XMLHttpRequest Level 2 中的 API,windows属性,可用于解决:
- 页面和其他打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
(4)websocket
实现了浏览器与服务器的全双工通信,websocket和http都是应用层协议,基于TCP协议,但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。
(5)node中间件代理(两次跨域)
(6)nginx 反向代理
使用nginx反向代理实现跨域,是最简单的跨域方式。
支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。
//porxy服务器
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pas http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
#当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
最后通过命令行nginx -s reload启动 nginx
// index.html
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
// server.js
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写cookie
res.writeHead(200, {
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
(7)window.name + iframe
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
其中 a.html 和 b.html 是同域的,都是http://localhost:3000;而 c.html 是http://localhost:4000
// a.html(http://localhost:3000/b.html)
<iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:3000/b.html';
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
b.html 为中间代理页,与 a.html 同域,内容为空。
// c.html(http://localhost:4000/c.html)
<script>
window.name = '我不爱你'
</script>
通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
(8)location.hash + iframe
实现原理: a.html 欲与 c.html 跨域相互通信,通过中间页 b.html 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。
具体实现步骤:一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。
同样的,a.html 和 b.html 是同域的,都是http://localhost:3000;而 c.html 是http://localhost:4000
// a.html
<iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
<script>
window.onhashchange = function () { //检测hash的变化
console.log(location.hash);
}
</script>
// b.html
<script>
window.parent.parent.location.hash = location.hash
//b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
</script>
// c.html
console.log(location.hash);
let iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
(9)document.domain + iframe
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。
实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。
我们看个例子:页面a.zf1.cn:3000/a.html获取页面b.zf1.cn:3000/b.html中 a 的值
// a.html
<body>
helloa
<iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'zf1.cn'
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
hellob
<script>
document.domain = 'zf1.cn'
var a = 100;
</script>
</body>
总结:
- CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案
- JSONP 只支持 GET 请求,JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
- 不管是 Node 中间件代理还是 nginx 反向代理,主要是通过同源策略对服务器不加限制。
- 日常工作中,用得比较多的跨域方案是 cors 和 nginx 反向代理
PWA
PWA全称Progressive Web App,即 渐进式WEB应用。
一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
解决了哪些问题?
可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
实现了消息推送
它解决了上述提到的问题,这些特性将使得 Web 应用渐进式接近原生 App。
更多可参看:https://segmentfault.com/a/1190000012353473?utm_source=tag-newest
原型与原型链
什么是原型?
在js中,所有对象都是Object的实例,并集成Object.prototype的属性和方法,但是有一些是隐性的。
所有引用类型都具有对象特性,可以自由扩展属性。
var obj = {};
obj.attribute = "new attr";
var arr = [];
arr.attribute = "new attr";
function fn () {}
fn.attribute = "new attr";
所有的引用类型(包括数组,对象,函数)都有隐性原型属性(proto), 值也是一个普通的对象。
console.log(obj.__proto__);
所有的函数,都有一个 prototype 属性,值也是一个普通的对象。
console.log(obj.prototype);
所有的引用类型的proto属性值都指向构造函数的 prototype 属性值。
console.log(obj.__proto__ === Object.prototype); // true
当试图获取对象属性时,如果对象本身没有这个属性,那就会去他的proto(prototype)中去寻找。
function Dog(name){
this.name = name;
}
Dog.prototype.callName = function (){
console.log(this.name,"wang wang");
}
let dog1 = new Dog("Three Mountain");
dog1.printName = function (){
console.log(this.name);
}
dog1.callName(); // Three Mountain wang wang
dog1.printName(); // Three Mountain
原型链图:作用域及闭包
只有函数才能创造作用域。
for if else 不能创造作用域。
this:
本质上来说,在 js 里 this 是一个指向函数执行环境的指针。this 永远指向最后调用它的对象,并且在执行时才能获取值,定义是无法确认他的值。
var a = {
name : "A",
fn : function (){
console.log (this.name)
}
}
a.fn() // this === a
a 调用了fn() 所以此时this为a
a.fn.call ({name : "B"}) // this === {name : "B"}
使用call(),将this的值指定为{name:"B"}
var fn1 = a.fn
fn1() // this === window
虽然指定fn1 = a.fn,但是调用是有window调用,所以this 为window
this 有多种使用场景:
- 作为构造函数执行:
function Student(name,age) {
this.name = name // this === s
this.age = age // this === s
//return this
}
var s = new Student("py1988",30)
- 作为普通函数执行:
function fn () {
console.log (this) // this === window
}
fn ();
- 作为对象属性执行:
var obj = {
name : "A",
printName : function () {
console.log (this.name) // this === obj
}
}
obj.printName ()
4.call(), apply(), bind(): 这是重点!
三个函数都可以修改this的指向:
var name = "小明" , age = "17"
var obj = {
name : "安妮",
objAge :this.age,
fun : function (like,dislike) {
console.log (this.name + "今年" + this.age ,"喜欢吃" + like + "不喜欢吃" + dislike) ;
}
}
var a = { name : "Jay", age : 23 }
obj.fun.call(a,"苹果","香蕉") // Jay今年23 喜欢吃苹果不喜欢吃香蕉
obj.fun.apply(a,["苹果","香蕉"]) // Jay今年23 喜欢吃苹果不喜欢吃香蕉
obj.fun.bind(a,"苹果","香蕉")() // Jay今年23 喜欢吃苹果不喜欢吃香蕉
首先 call,apply,bind 第一个参数都是 this 指向的对象,call 和 apply 如果第一个参数指向 null 或 undefined 时,那么 this 会指向 windows 对象。
call,apply,bind 的执行方式如上例所示。call,apply 都是改变上下文中的 this,并且是立即执行的。bind 方法可以让对应的函数想什么时候调用就什么时候调用。
闭包:
闭包的概念很抽象,看下面的例子你就会理解什么叫闭包了:
function a(){
var n = 0;
this.fun = function () {
n++;
console.log(n);
};
}
var c = new a();
c.fun(); //1
c.fun(); //2
闭包就是能够读取其他函数内部变量的函数。在 js 中只有函数内部的子函数才能读取局部变量。所以可以简单的理解为:定义在内部函数的函数。
异步和单线程
我们先感受下异步。
console.log("start");
setTimeout(function () {
console.log("medium");
}, 1000);
console.log("end");
使用异步后,打印的顺序为 start-> end->medium。因为没有阻塞。
为什么会产生异步呢?
首先因为 js 为单线程,也就是说 CPU 同一时间只能处理一个事务。得按顺序,一个一个处理。
如上例所示,第一步:执行第一行打印 “start”;第二步:执行 setTimeout,将其中的函数分存起来,等待时间结束后执行;第三步:执行最后一行,打印 “end”;第四部:处于空闲状态,查看暂存中,是否有可执行的函数;第五步:执行分存函数。
为什么 js 引擎是单线程?
js 的主要用途是与用户互动,以及操作 DOM,这决定它只能是单线程。例:一个线程要添加 DOM 节点,一个线程要删减 DOM 节点,容易造成分歧。
为了更好使用多 CPU,H5 提供了 web Worker 标准,允许 js 创建多线程,但是子线程受到主线程控制,而且不得操作 DOM。
任务列队
单线程就意味着,所有的任务都要排队,前一个结束,才会执行后面的任务。如果列队是因为计算量大,CPU 忙不过来,倒也算了。但是更多的时候,CPU 是闲置的,因为 IO 设备处理得很慢,例如 ajax 读取网络数据。js 设计者便想到,主线程完全可以不管 IO 设备,将其挂起,然后执行后面的任务。等后面的任务结束掉,在反过头来处理挂起的任务。
好,我们来梳理一下:
1)所有的同步任务都在主线程上执行,行程一个执行栈。
2)除了主线程之外,还存在一个任务列队,只要一步任务有了运行结果,就在任务列队中植入一个时间。
3)主线程完成所有任务,就会读取列队任务,并将其执行。
4)重复上面三步。
只要主线程空了,就会读取任务列队,这就是 js 的运行机制,也被称为 event loop(事件循环)。
ES6 语法
ES6,强制开启严格模式!
let 和 const
作用域的概念:
全局作用域,函数作用域,块作用域
<暂时性死区>
let和const不能重复定义;const声明时必须赋值,let 不必须;
解构赋值
解构赋值的分类:
数组解构赋值,对象解构赋值,字符串解构赋值,
布尔值解构赋值,函数参数解构赋值,数值解构赋值
使用场景:
数组结构赋值:
(1)默认值:
function fn(a, b=2){
...
}
(2)变量交换
{
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);
}
(3)获取函数返回值
{
function f(){
return [1,2,3,4];
}
let a, b, c, d;
[a, b,c,d] = f(); // 接收所有值
[a, , , b] = f(); // 接收指定值
[a, ...b] = f(); // 分开接收
}
对象解构赋值:
{
let o = {p:42, q:true}
let {p,q} = o;
console.log(p,q);
}
(1)默认值:
{
let {a = 5, b = 10} = {a : 3}
console.log(a,b) // 3, 5
}
(2)json对象取值 :特别常用的场景!
{
let data = {
title: 'abc',
test: [{
title: 'test',
desc: '描述'
}]
}
// 声明一个跟解构对象格式一致的对象,来接收对象中的值
let {title : esTitle, test: [{title: cnTitle}]} = data;
console.log(esTitle, cnTitle);
}
正则扩展:
构造函数的变化、正则方法扩展、u修饰符、y修饰符、s修饰符
es5中有一下两种正则表达式写法:
{
// 两个参数的情况,前面是匹配字符串
let regex = new RegExp('xyz', 'i');
// 一个参数的情况,前面是正则表达式
let regex2 = new RegExp(/xyz/i);
}
es6中,允许前面是正则表达式,而后面跟着修饰符的两个参数的写法:
{
let regex3 = new RegExp(/xyz/ig, 'i'); // 但这种写法后面的修饰符会覆盖正则中的修饰符
// flags是es6中正则表达式的新的API,输出的结果是正则表达式的修饰符
console.log(regex3.flags); // 输出的是 ' i '
}
y修饰符:
{
let s = 'bbb_bb_b';
let a1 = /b+/g;
let a2 = /b+/y;
console.log('one', a1.exec(s), a2.exec(s));
console.log('two', a1.exec(s), a2.exec(s));
}
g和y修饰符的共同点:都是全局匹配
不同点:
g:可以不连续匹配,bbb_bb_b
y: 粘连模式,必须连续匹配b后面如果不是b了,就不会继续匹配
sticky可以查看是否开启了粘连模式:a1.sticky
u修饰符:
u修饰符,可以识别大于2个字节的字符;
es5中,. 能匹配任何字符,但是前提是小于两个字节的
s修饰符:es6中还未实现
字符串扩展:(要安装babel-polyfill)
处理unicode编码大于0xffff的字符 => 用{}包裹起来
`\u20BB7` => `\u{20BB7}`
es5中:
获取unicode编码对应的字符:fromCharCode()
;
获取字符对应的unicode编码:charCodeAt()
;
es6中:
获取unicode编码对应的字符:fromCharPoint()
;
获取字符对应的unicode编码:codePointAt()
; // 能获取超过0xFFFF长度的编码,并正确显示
es6中有一个字符串遍历器接口,可以正确处理unicode编码大于0xFFFF的字符串
{
let str = '/u{20BB7}abc';
for(let code of str){
console.log('es6', code);
}
//let of 可以正确遍历出0xFFFF编码长度的字符串对应的字符,不会出现乱码
}
str.includes('x')
:判断字符串中是否包含了某个字符
str.startsWith('x')
:判断字符串是不是已某个字符开始的
str.endsWith('x')
:判断字符串是不是已某个字符结束的
str.repeat(2)
:重复字符串
模板字符串(很重要):
`你好,${name},欢迎你!`
padStart 和 padEnd (这两个是ES7的草案,需要babel-polyfill)
console.log('1'.padStart(2, '0')); // '01'
console.log('1'.padEnd(2, '0')); // '10'
标签模板:
1.怎么用?
{
let user = {
name: 'list',
info: 'hello world'
};
console.log(abc`i am ${user.name}, ${user.info}`);
function abc(s, v1, v2){
console.log(s, v1, v2);
return s+v1+v2;
}
}
2.在哪里用?
防止XSS攻击,和处理多语言转换的时候用
string.raw:可以将所有的 ' \ ' 进行了转义,即在前面又加了一个
String.raw`Hi\n${1+2}` // Hi\n3
数值扩展:
Number.isFinite()
:判断一个值是否有尽
Number.isNaN()
:判断一个数是不是NaN
Number.isInteger()
:判断是不是整数 2.0也是true
Number.MAX_SAFE_INTEGER
// 最大安全数值
Number.MIN_SAFE_INTEGER
// 最小安全数值
Number.isSafeInteger()
// 判断一个数字是不是在安全数的范围内:
Math.trunc(4.1); //4
// 取小数的整数部分
Math.sign() // 结果只有-1, 0 ,1, NaN
// 判断是正数,负数,还是零
Math.cbrt(8); // 2
// 计算立方根
还有三角函数方法,对数方法,都是es6新增的
数组扩展:
Array.from
Array.of
copyWithin
find \ findIndex
fill
entries \ keys \ values
includes
函数扩展:
1、参数默认值
{
function test(x, y = " 23 "){
console.log(x, y);
}
test(2) // 2 23
}
// 要注意默认值后面不能再有没有默认值的参数变量
// 这就是错误的
function test(x, y = " 23 " , c) ...
关于取值作用域的问题:
{
let x = 'test';
function test(x, y=x){
console.log(x,y);
}
test('kill'); // kill kill
}
2、rest参数
把一系列的参数都转换为数组,在你不确定有多少个参数的时候
注意,rest参数后面不能再有其他的参数
{
function test( ...arg ){
for (let v of arg){
console.log(v);
}
}
test(1,2,3,4); // 1 2 3 4
}
3、扩展运算符
扩展运算符和rest运算符是一个相反的作用
它的作用是:把一个数组拆成离散的值
{
console.log(...[1,2,3]); // 1 2 3
console.log('a', ...[1,2,3]) // 'a' 1 2 3
}
4、箭头函数
{
let arrow = v => v*2;
// 等同于
function arrow(v){
return v*2;
}
// 无参数
let arrow = () => v*3;
// 等同于
function arrow(){
return v*3;
}
}
5、this绑定
因为this在指向在箭头函数和普通函数不一样,所以要考虑什么时候适合用箭头函数,什么时候不适合用箭头函数;
this在普通函数中,指向函数被调用时的对象所在;
this在箭头函数中,指向函数被定义时的对象所在;
6、尾调用
区分特征:函数的最后一句话是不是函数
{
function tail(x){
console.log('tail', x);
}
function fx(x){
return tail(x);
}
fx(123);
}
// 尾调用的用处:提升(递归函数,嵌套函数)性能
对象(object)扩展:
1、简介表示法:
{
let o = 1;
let k = 2;
// es5中声明一个对象
let es5 = {
o: o,
k: k
};
// es6中,如果键值相同,可以这样写:
let es6 = {
o,
k
};
// 如果对象里面有方法:
let es5_method = {
hello: function(){}
};
let es6_method = {
hello(){}
}
}
2、属性表达式:
{
let a = 'b';
let es5_obj = {
a: 'c',
b: 'c'
};
// 对象的key值,可以是一个表达式,这叫做属性表达式
// 这里的 [a] = 'b';
let es6_obj = {
[a]: 'c'
}
console.log(es5_obj, es6_obj);
}
3、扩展运算符:
babel对它的支持不是很好,加了babel-polyfill还是会报错
{
let {a, b, ...c} = {a: 'test', b:'re', c:'ccc', d:'ddd'};
解析过后c的值应该是:
c: {
c:'ccc',
d:'ddd'
}
}
4、Object新增方法:
// 新增API
Object.is() 的功能和 ‘===’ 没有区别,判断两个值是否相等
Object.is(str, str); //判断两个字符串是否相等
// 要注意,数组和对象是引用类型,虽然都是空数组,但是引用的地址不同,所以:
object.is([],[]) // 结果是false
Object.assign() : 浅拷贝
// 拷贝的是引用地址,不是值;
// 只拷贝自身的属性,不会拷贝继承的属性,和不可枚举的属性
Object.assign({a:''a}, {b:'b'}); // {a: 'a', b: 'b'}
Object.entries() : 方法返回一个给定对象自身可枚举属性的键值对数组,
其排列与使用 for...in 循环遍历该对象时返回的顺序一致
区别在于 for-in 循环也枚举原型链中的属性
const object1 = { foo: 'bar', baz: 42 };
console.log(Object.entries(object1)[1]);
// expected output: Array ["baz", 42]
const object2 = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(object2)[2]);
// expected output: Array ["2", "c"]
const result = Object.entries(object2).sort((a, b) => a - b);
console.log(Object.entries(result)[1]);
// expected output: Array ["1", Array ["1", "b"]]
Symbol:
1、Symbol的概念:
Symbol可提供一个独一无二的值
let a1 = Symbol();
let a2 = Symbol();
console.log(a1 === a2); // false
// 定义了以后,将来要取回来,就用这种方式
let a3 = Symbol.for('a3'); // for中的a3是一个key值,如果在全局注册过,就返回这个值,如果没有注册过,就注册一个独一无二的值
let a4 = Symbol.for('a3');
let a5 = Symbol.for('a3');
console.log(a4 === a5); // true
2、Symbol的作用:
{
let a1 = Symbol.for('abc');
let obj = {
[a1] : '123',
'abc' : 234,
'c' : 456
}
console.log('obj', obj); // {Symbol(abc): '123', 'abc': 234, 'c': 456}
要注意一点:用Symbol做key值,通过for...in
和let...of
是拿不到key值的
for(let [key, value] of Object.entries(obj)){
console.log(key, value) ; //拿不到Symbol(abc)和其值
}
这时可以通过Object.getOwnPropertySymbols()
拿到Symbol定义的值,但是它拿不到其他的值
Object.getOwnPropertySymbols(obj).forEach(function(item){
console.log(obj[item]); // 123
})
如果想拿到所有的值,可以通过Reflect.ownKeys()
拿到
Reflect.ownKeys(obj).forEach(function(item){
console.log('ownKeys', item, obj[item])
})
ES6数据结构:
Set:
1、类似数组,但集合中的元素不能重复
{
let list = new Set();
list.add(5); // 添加
list.add(7);
list.size // 获取set中的长度(个数)
// 初始化set中的值
let arr = [1,2,3,4,5];
let list1 = new Set(arr);
// 不可重复性
let list2 = new Set();
list2.add(1);
list2.add(2);
list2.add(1);
console.log(list2); // 不会打印出后面重复的元素,也不会报错,这可以用于去重
这里要注意,Set不会做数据类型的转换,即 2 和 '2' 在set看来是不一样的
2、Set实例的几个方法和属性值:
add()
:添加某个元素
delete()
:删除某个元素
clear()
:清空所有元素
has()
:判断有没有某个元素,返回布尔类型
size
:返回集合中元素的个数
3、Set实例的遍历:
// for...of
for(let key of list.keys()){
console.log('keys', key);
}
for(let value of list.values()){
console.log('value', value);
}
for(let value of list){
console.log('value', value);
}
for(let [key, value] of list.entries()){
console.log('entries', key, value);
}
key和value的值一样,都是元素的名称;
不加.key或者.values,也是可以的
Map
1 、类似Object,key可以是任意类型(object中key只能是字符串)
{
let map = new Map();
let arr = ['123'];
map.set(arr, 456); // Map可以用数组作为key(或其他任意类型)哦~
console.log('map', map, map.get(arr));
// 结果:map Map{["123"] => 456} 456
// 初始化Map中的值
let map1 = new Map([['a', 123], ['b', 456]]); // 一定要注意这种格式
}
2、Map实例的几个方法和属性值:
get()
:获取某个元素
set()
:添加某个元素
delete()
:删除某个元素
clear()
:清空所有元素
has()
:判断有没有某个元素,返回布尔类型
size
:返回集合中元素的个数
3、Map 结构转为数组结构
比较快速的方法是结合使用扩展运算符(...)
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
4、Map 循环遍历
Map 原生提供三个遍历器:
keys()
:返回键名的遍历器。
values()
:返回键值的遍历器。
entries()
:返回所有成员的遍历器。
let map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// 等同于使用map.entries()
// 这个例子可以看出:Map 结构的默认遍历器接口(Symbol.iterator 属性),就是 entries 方法。
for (let [key, value] of map) {
console.log(key, value);
}
5、Map 获取第一个元素
const m = new Map();
m.set('key1', {})
m.set('keyN', {})
console.log(m.entries().next().value); // [ 'key1', {} ]
// 获取第一个key
console.log(m.keys().next().value); // key1
// 获取第一个value
console.log(m.values().next().value); // {}
Map与Array的对比 、 Set与Array的对比
数据结构横向对比,增、查、改、删
// Map与Array
let map = new Map();
let array = [];
// 增
map.set('t', 1);
array.push({t:1});
console.info('may-array', map, array);
// 查
let map_exist = map.has('t');
let array_exist = array.find(item=>item.t);
console.info('may-array', map_exist, array_exist); //map返回的是布尔值,数组返回的是这个值
// 改
map.set('t', 2);
array.forEach(item => item.t ? item.t = 2 : ' '); // 先要判断是否存在,才执行
console.info('may-array-modify', map, array);
// 删
map.delete('t');
let index = array.findIndex(item => item.t); // 查找每一个元素中带t的元素
array.splice(index, 1);
console.info('may-array-del', map, array);
// Set与Array
let map = new Set();
let array = [];
// 增
set.add({t :1});
array.push({t:1});
console.info('set-array', set, array);
// 查
let set_exist = set.has({t:1}); //这里应该是填入保存的地址,这样写会报错
let array_exist = array.find(item=>item.t);
console.info('may-array', set_exist, array_exist); //set返回的是布尔值,数组返回的是这个值
// 改
set.forEach(item => item.t ? item.t = 2: ' ');
array.forEach(item => item.t ? item.t = 2 : ' '); // 先要判断是否存在,才执行
console.info('may-array-modify', set, array);
// 删
set.forEach(item => item.t ? set.delete(item) : ' ');
let index = array.findIndex(item => item.t); // 查找每一个元素中带t的元素
array.splice(index, 1);
console.info('may-array-del', set, array);
Proxy和Reflect
Proxy:可以用来代理对对象的访问
Reflect 的方法和用法和Proxy一模一样。只是使用方式不一样,Proxy需要new,而Reflect直接使用(Reflect.get())
// Proxy的几个常用方法:
// 其中target为代理对象参数,原本obj的属性,它都有,实际传入的代理对象,是proxy的实例化对象
// 获取对象属性
get(target, key){}
// 设置对象属性
set(target, key, value){}
// 判断对象是否有某个属性,可以拦截判断,欺骗访问
has(target, key){}
// 删除对象属性:拦截delete,如果不想用户删除对象中的某些属性
deleteProperty(target, key){}
// 拦截Object.keys, Object.getOwnPropertySymbols, Object.getOwnPropertyNames
ownKeys(){}
完整示例,及Proxy在开发中的使用:
{
let obj = {
time: '2017-03-11',
name: 'net',
_r: 123
}
let monitor = new Proxy(obj, {
// 拦截对象属性的读取
get(target, key) {
return target[key].replace('2017', '2018')
},
// 拦截对象设置属性
set(target, key, value) {
if (key === 'name') {
return (target[key] = value)
} else {
return target[key]
}
},
// 拦截key in object操作
has(target, key) {
if (key === 'name') {
return target[key]
} else {
return false
}
},
// 拦截delete
deleteProperty(target, key) {
if (key.indexOf('_') > -1) {
delete target[key]
return true
} else {
return target[key]
}
},
// 拦截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames
ownKeys(target) {
return Object.keys(target).filter(item => item != 'time')
}
})
console.log('get', monitor.time)
monitor.time = '2018'
monitor.name = 'mukewang'
console.log('set', monitor.time, monitor)
console.log('has', 'name' in monitor, 'time' in monitor)
// delete monitor.time;
// console.log('delete',monitor);
//
// delete monitor._r;
// console.log('delete',monitor);
console.log('ownKeys', Object.keys(monitor))
}
{
let obj = {
time: '2017-03-11',
name: 'net',
_r: 123
}
console.log('Reflect get', Reflect.get(obj, 'time'))
Reflect.set(obj, 'name', 'mukewang')
console.log(obj)
console.log('has', Reflect.has(obj, 'name'))
}
// proxy常用的情景:对数据类型进行校验,实现与业务解耦
{
function validator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
// 判断是否有对应可操作的属性
if (target.hasOwnProperty(key)) {
let va = this._validator[key]
if (!!va(value)) {
// 如果所访问的属性有值,则执行代理操作(这里仅以Set为例),将代理返回到真实对象proxy
return Reflect.set(target, key, value, proxy)
} else {
throw Error(`不能设置${key}到${value}`)
}
} else {
// 如果没有,则抛出异常
throw Error(`${key} 不存在`)
}
}
})
}
// 模拟一个条件类
const personValidators = {
name(val) {
return typeof val === 'string'
},
age(val) {
return typeof val === 'number' && val > 18
},
mobile(val) {}
}
// 模拟一个被代理的类
class Person {
constructor(name, age) {
this.name = name
this.age = age
this.mobile = '1111'
// 这里的this,指的是Person的实例化对象
// Person类返回一个validator方法,validator方法返回一个proxy代理,
//也就是说访问Person的实例,会经过validator中的proxy代理,进行筛选过滤
return validator(this, personValidators)
}
}
// 实例化Person类
const person = new Person('lilei', 30)
console.info(person)
person.name = 'Han mei mei'
console.info(person)
}
ES6中的 类和对象
类
基本语法 | 类的继承 | 静态方法 | 静态属性 | getter | setter
1、基本定义和生成实例
class Parent{
// 定义类中的构造函数
constructor(name = 'zk'){
this.name = name;
}
}
let v_parent = new Parent('z');
console.log('构造函数实例', v_parent)
2、类的继承
定义一个父类,子类来继承
class parent{
constructor(name='zk'){
this.name = name;
}
}
class child extends parent{
// 继承父类的子类,可以覆盖 / 把实际需要的参数传递给 父类的参数
constructor(name = 'child'){
super(name); //要覆盖父类哪个参数,就传递哪个参数名;如果不传,则默认全部使用父类参数的默认值
// 如果子类还要增加自己的属性,一定要放在super之后
this.type = 'child';
}
}
console.log('继承', new Child('hello'));
getter 和 setter
class Parent{
constructor(name = 'zk'){
this.name = name;
}
// getter
// 注意,get后面的是属性,不是方法!
get longName(){
return 'mk' + this.name
}
// setter
set longName(value){
this.name = value
}
}
// get操作
let v = new Parent();
console.log('getter',v.longName);
// set操作
v.longName = 'hello'
console.log('setter',v.longName);
静态方法 的定义和使用
// static只是用来定义静态方法,不是用来定义静态属性用的!
// 静态属性是直接在类上定义的。
class Parent{
constructor(name = 'zk'){
this.name = name;
}
static tell(){
console.log('tell')
}
}
Parent.tell(); // tell
静态属性的定义和使用(静态属性没有关键词(暂时没有,虽然有提案))
class Parent{
constructor(name = 'zk'){
this.name = name;
}
static tell(){
console.log('tell')
}
}
Parent.type = 'type'; // 直接在类上定义属性,不用new实例
console.log('静态属性', Parent.type);
思考:什么样的场景下适合用静态方法,什么样的场景下适合用静态属性?
Promise
什么是异步 | Promise的作用 | Promise的基本用法
现在的js异步编程的方式有一下几种:
1.回调函数 2.事件监听 3.发布/订阅 4.Promise 对象 5.Generator
之前有两种方式:通过回调函数、通过触发方法
而现在的Promise更适合异步操作
// 通过模拟ajax请求,来使用promise实现异步操作
{
let ajax = function(){
console.log('执行1');
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve()
}, 1000)
})
}
ajax().then(function(){
console.log("然后执行2");
})
}
如果是多个异步操作呢?
// 在then()中继续使用promise即可
{
let ajax=function(){
console.log('执行3');
return new Promise(function(resolve, reject){
setTimeout(function () {
resolve()
}, 1000);
})
};
ajax().then(function(){
return new Promise(function(resolve, reject){
setTimeout(function () {
resolve()
}, 2000);
});
}).then(function(){
console.log('timeout3');
})
}
Promise 提供 then 方法加载回调函数,catch方法捕捉执行过程中抛出的错误。catch捕获出错信息,err就是具体的出错信息
{
let ajax=function(num){
console.log('执行4');
return new Promise(function(resolve,reject){
if(num>5){
resolve()
}else{
throw new Error('出错了')
}
})
}
ajax(6).then(function(){
console.log('log',6);
}).catch(function(err){
console.log('catch',err);
});
ajax(3).then(function(){
console.log('log',3);
}).catch(function(err){
console.log('catch',err);
});
}
promise的高级用法
promise.all promise.race(赛跑)
情景1:保证所有图片都加载完成,再显示页面
{
// 所有图片加载完再添加到页面
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(err){
reject(err);
}
})
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img);
})
}
Promise.all([
loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),
loadImg('http://i4.buimg.com/567751/2b07ee25b08930ba.png'),
loadImg('http://i2.muimg.com/567751/5eb8190d6b2a1c9c.png')
]).then(showImgs)
}
情景2:同一个资源有多个来源,只要一个来源请求成功,就添加到页面,其他的忽略不管(先到先得)
{
// 有一个图片加载完就添加到页面
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(err){
reject(err);
}
})
}
function showImgs(img){
let p=document.createElement('p');
p.appendChild(img);
document.body.appendChild(p)
}
Promise.race([
loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),
loadImg('http://i4.buimg.com/567751/2b07ee25b08930ba.png'),
loadImg('http://i2.muimg.com/567751/5eb8190d6b2a1c9c.png')
]).then(showImgs)
}
Iterator 和 for...of 循环
什么是Iterator接口 | Iterator的基本用法 | for...of 循环
for...of只能遍历数组,如果其他数据格式也想通过for...of来遍历,前提条件是它本身自带Iterator接口,如果没有,则需要为其自定义Iterator接口
数组支持遍历,因为它本身已经封装好了Iterator接口:
// arr本身有一个Iterator的方法,通过arr[Symbol.iterator]()可以触发,这个方法会返回一个对象,对象有一个next()方法,用来输出每次遍历的结果
{
let arr=['hello','world'];
let map=arr[Symbol.iterator]();
console.log(map.next());
console.log(map.next());
console.log(map.next());
结果:
Object{value: 'hello', done: false}
Object{value: 'world', done: false}
Object{value: undefined, done: true}
}
如果其他没有Iterator接口的数据类型,也想实现遍历,则可以自定义Iterator接口
{
let obj={
start:[1,3,2],
end:[7,9,8],
[Symbol.iterator](){
let self=this;
let index=0;
let arr=self.start.concat(self.end);
let len=arr.length;
return {
// 注意:这里要返回一个对象,对象中包含next()方法,而且返回值有两个,value和done
next(){
if(index<len){
return {
// 注意每遍历一次,index都要+1
value:arr[index++],
done:false
}
}else{
return {
value:arr[index++],
done:true
}
}
}
}
}
}
// 本身for...of只能用来遍历数组,但通过为obj自定义了Iterator接口,从而使for...of也可以遍历obj
for(let key of obj){
console.log(key);
}
}
说到for...of,就来回顾一下js中,数据的操作方法 和 几个循环的区别:
数组的操作方法:
会改变原数组的有:pop push unshift shift splice reverse sort
不会改变原数组的有:indexOf lastIndexof concat slice
其他常用操作方法:
es5的有:forEach filter map some every reduce
es6的有:find includes
几个循环方法及其区别:
forEach、for、for...in、for..of
1、for (编程式)
let arr = [1,2,3,4,5];
for(let i = 0;i<arr.length;i++){
console.log(arr[i]);
}
2、forEach (声明式:不关心如何实现)
// 不支持return,它会遍历到结束
let arr = [1,2,3,4,5];
arr.forEach(function(item){
console.log(item);
})
3、for...in (平时不用)
支持return,但一般不用它遍历数组
1- 因为索引不是数字,是字符串;
2- 因为for...in还会遍历出数组的私有属性,会影响length的准确性
let arr = [1,2,3,4,5];
arr.b = '100';
for(let key in arr){
// key会变成字符串类型
console.log(key) // 会把私有属性b也遍历出来
}
4、for...of (es6语法)
// 用于遍历数组
// 既可以return,又不会遍历出私有属性
let arr = [1,2,3,4,5];
for(let value of arr){
console.log(value);
}
// 如果想让for...of遍历对象,不仅可以通过Iterator添加接口的方式,也可以通过Object.keys()来变通实现
let obj = {
school: 'zk',
age: 9
}
for(let value of Object.keys(obj)){
console.log(obj[value])
}
filter的用法:
是否操作原数组:不操作
返回结果:过滤后的新数组
回调函数返回结果:如果返回true,则将这一项放入新数组中
// 如果想筛选(删除)某些项,可以用filter
{
let arr = [1,2,3,4,5];
let newArr = arr.filter(function(item){
return item>2 && item<5;
});
console.log(newArr); // [3,4]
}
map(映射)的用法:
将原有的数组映射成一个新的数组
是否操作原数组:不操作
返回结果:映射后的新数组
回调函数返回结果:回调函数中返回什么,这一项就是什么
// 可以用于将json返回值,拼接成dom元素,即更新某些项,可以用map
{
let arr1 = [1,2,3];
let newArr1 = arr1.map(function(item){
return `<li>${item}</li>`
});
console.log(newArr1.join('')); // <li>1</li><li>2</li><li>3</li>
}
includes的用法:
看数组中是否包含某个值
是否操作原数组:不操作
返回结果:判断结果布尔值
回调函数返回结果:包含则返回true,否则返回false
{
let arr3 = [1,2,3,4,5,6];
console.log(arr3.includes(5));
}
find的用法:
找到数组中的某个值
是否操作原数组:不操作
返回结果:判断找到的值
回调函数返回结果:回调中返回true,表示找到了,找到后即停止遍历;找不到返回undefined
{
let arr4 = [1,2,3,4,5,55,6];
let result = arr4.find(function(item, index){
return item.toString().indexOf(5) > -1
})
console.log(result);
}
some的用法:
找到true后停止,返回true ;类似 “或”
是否操作原数组:不操作
返回结果:判断数组中是否存在某值符合条件
回调函数返回结果:判断有一个条件成立(或存在某值),就返回true,并立即停止
{
let arr4 = [1,2,3,4,5];
let result = arr4.some(function(item, index){
return item.toString().indexOf(5) > -1
})
console.log(result);
}
every的用法:
找到false后停止,返回false;类似 “与”
是否操作原数组:不操作
返回结果:判断数组中是否所有的值都符合条件
回调函数返回结果:判断一旦有一个条件不符合(或不存在某值),就返回false,并立即停止
{
let arr4 = [1,2,3,4,5,55,6];
let result = arr4.every(function(item, index){
return item.toString().indexOf(5) > -1
})
console.log(result);
}
reduce(翻译:收敛,使还原,使变弱)
有四个参数(prev, next, index, item)
用法:将上一次的结果作为下一次的第一个,继续和后面的数据进行操作
即1和2的结果,与3操作,他们的结果再与4操作,以此类推
是否操作原数组:不操作
返回结果:返回叠加后的结果
回调函数返回结果:返回回调函数返回的结果
`
{
let arr4 = [1,2,3,4,5];
//第一次 prev:代表数组的前一项;next:代表数组的第二项
//第二次 prev:是数组的上一次返回值;next:是数组的第三项
//如果没有返回值,则第二次的prev便是undefined
let reduce = arr4.reduce(function(prev, next, index, item){
// item为原数组
console.log(arguments);
// return 100;
})
console.log(reduce );
}
应用举例:
1、计算多个商品的单价和数量的价格总和;
2、将二维数组,变成一维数组;
// 应用1:
let sum2 = [{price:30,count:2},{price:30,count:3},{price:30,count:4} ].reduce(function(prev, next){
console.log(prev, next)
return prev+next.price*next.count
}, 0); // 这个0,是默认执行第一次的prev(即默认第一项的值)
// reduce有两个参数:第一个是方法,第二个是默认第一项
// 应用2:
let arrs = [[1,2,3],[4,5,6],[7,8,9]];
let flat = arrs.reduce(function(prev,next){
return prev.concat(next);
});
console.log(flat); // [1,2,3,4,5,6,7,8,9]
Generator(发生器)
异步编程解决方案(相对promise更高级)
基本概念 | next函数的用法 | yield*的用法
Generator是什么?
举例来说,读取文件的协程写法如下。
functionasnycJob(){
// ...其他代码
var f=yield readFile( fileA);
// ...其他代码
}
上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。
以上举例转载自:https://www.jianshu.com/p/56ec5c113f1b,如要了解关于Generator,可参看。
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
Generator包含next和yield,一旦步骤遇到yield或return,就停止,遇到next就进行下一步,直到结束
Generator函数定义:
// 需要babel-polyfill支持
{
// genertaor基本定义
let tell=function* (){
yield 'a';
yield 'b';
return 'c'
};
let k=tell();
console.log(k.next());
console.log(k.next());
console.log(k.next());
console.log(k.next());
}
为其他数据结构部署Iterator接口,实现遍历,不光可以按照之前Iterator接口手动实现的写法,还可以使用Generator。
Generator就是一个遍历器生成接口
Generator 应用,实现遍历器接口(一种应用情景)
{
let obj={};
obj[Symbol.iterator]=function* (){
yield 1;
yield 2;
yield 3;
}
for(let value of obj){
console.log('value',value);
}
}
Generator最大的优势 : 状态机
比如一个对象只有三种状态,A,B,C ,他们会在这三种状态中循环出现。
{
let state = function* (){
// 因为一直都是true,所以会一直循环
while(1){
yield 'A';
yield 'B';
yield 'C';
}
}
let status = state();
console.log(status.next());// Object {value: 'A', done: false}
console.log(status.next());// Object {value: 'B', done: false}
console.log(status.next());// Object {value: 'C', done: false}
console.log(status.next());// Object {value: 'A', done: false}
console.log(status.next());// Object {value: 'B', done: false}
...
}
async...await 语法是Generator函数的一个语法糖
{
let state=async function (){
while(1){
await 'A';
await 'B';
await 'C';
}
}
let status=state();
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
console.log(status.next());
}
Generator的应用:
1、剩余抽奖次数逻辑
{
let draw=function(count){
//具体抽奖逻辑
console.info(`剩余${count}次`)
}
let residue=function* (count){
while (count>0) {
count--;
yield draw(count);
}
}
let star=residue(5);
let btn=document.createElement('button');
btn.id='start';
btn.textContent='抽奖';
document.body.appendChild(btn);
document.getElementById('start').addEventListener('click',function(){
star.next();
},false)
}
2、长轮巡
{
// 长轮询
let ajax=function* (){
yield new Promise(function(resolve,reject){
//模拟每200毫秒请求一次
setTimeout(function () {
resolve({code:0})
}, 200);
})
}
let pull=function(){
// generator实例
let genertaor=ajax();
let step=genertaor.next();
// step.value就是Promise实例
step.value.then(function(d){
if(d.code!=0){
setTimeout(function () {
console.info('wait');
pull()
}, 1000);
}else{
console.info(d);
}
})
}
pull();
}
Decorator(修饰器)[针对类]
基本概念 :
修饰器是一个函数,用来修改类的行为(或扩展类的功能)
使用准备:
1、除了babel-polyfill,还需要安装一个插件包:
babel-plugin-transform-decorators-legacy
2、修改.babelrc文件
{ "presets":["es2015"], "plugins":["transform-decorators-legacy"] }
3、再重启服务
基本用法:
声明函数体:
{
// 声明一个修饰器
let readonly = function(target, name, descriptor){
descriptor.writable = false; //描述是否可写
return descriptor
}
}
使用(在类的里面):
{
class Test{
@readonly
time(){
return '2018-03-09'
}
}
}
let test = new Test();
// 尝试修改类
test.time = function(){
console.log('reset time');
}
console.log(test.time());
//会报错提示无法修改只读属性time
Uncaught TypeError: Cannot assign to read only property 'time' of object '#<Test>'
在类的外面使用:
{
let typename=function(target,name,descriptor){
target.myname='hello'; // target指的就是类本身
}
@typename
class Test{
}
console.log('类修饰符',Test.myname);
// 第三方库修饰器的js库:core-decorators;
// 可以通过npm安装:npm install core-decorators
// import 进来以后,直接就可以@使用
}
Decorator的应用:
做埋点,日志统计
把埋点系统抽离出来,成为一个可复用的模块
{
let log=(type)=>{
return function(target,name,descriptor){
let src_method=descriptor.value;
descriptor.value=(...arg)=>{
src_method.apply(target,arg);
console.info(`log ${type}`);
}
}
}
class AD{
@log('show')
show(){
console.info('ad is show')
}
@log('click')
click(){
console.info('ad is click');
}
}
let ad=new AD();
ad.show();
ad.click();
}