ES6

ES6常用

原文地址 https://www.cnblogs.com/ainyi/p/8537027.html

变量声明 let 和const

let声明的变量不存在预解析(不存在变量提升)

let声明的变量不允许重复(在同一个作用域内,不同作用域运行重复)

块级作用域

ES6引入了块级作用域

块内部定义的变量,在外部是不可以访问的

    if(true){
        /*var a=123;*/
        let a=123;
    }
    console.log(a);
//var输出123 let会报错

 

{
这里是块级作用域
let a =123;
}
for (let i=0;i<3;i++){
console.log(i)
}
console.log(i)//报错

 在块级作用域内部,变量只能先声明再使用

if(true){
console.log(flag);
let flag=123;
}//报错

const用来声明常量

const n=1;
 n=2;

const声明的常量不允许重新赋值,const声明的常量必须初始化(就是必须一开始赋值)

字符串拼接

``es6拼接

 用``来进行模板拼接  ${ 里面放变量或者函数或者表达式} 比如 ${x+y}  ${add(x,y)}

    var a="送送送";
    var b="来来来";
    var c=`上路来人${a}下路拉人${b}`;
    console.log(c);

展开语法

可以在函bai数调用、数组构du造、构造字面量对象时, 将数据展开赋值。

JSX中用在组bai件属性上可以将对象的属性展开到组件上,传递给组件props

1、函数调用,展开数组参数

function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
sum(...numbers);


2、构造数组,展开数组 const arr1 = [3,4,5]; const arr2 = [0, 1, 2, ...arr1];    // [0,1,2,3,4,5] 3、构造数组,展开字符串 const str = 'abc'; const arr = [...str];    // ['a', 'b', 'c'] 4、构造对象,展开对象 const obj1 = {a: 1, b: 2, c: 3}; const obj2 = {...obj1, d: 4};    // {a: 1, b: 2, c: 3, d: 4} const obj3 = {...obj1, c: 4};    // {a: 1, b: 2, c: 4} const obj4 = {c: 4, ...obj1};    // {a: 1, b: 2, c: 3}
5、JSX组件属性展开 render(){     const settings = {         value: 1,         placeholder: '输入数值'     };     return <MyInput {...settings}/>     // 上面的写法类似于     // return <MyInput      //          value={settings.value}      //          placeholder={settings.placeholder}/> }

 

解构赋值

// 以前我们给变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
console.log(a,b,c); // 1 2 3

// 现在用解构赋值的写法就变得简单了,只要模式匹配上了就行了,如下
// 注意数组是有顺序的
var [a,b,c] = [11,22,33];
console.log(a,b,c); // 11 22 33

var [b,a,c] = [11,22,33];
console.log(a,b,c); // 22 11 33

// 当然解构赋值还有嵌套比较复杂的写法,如下
let [foo,[[bar],[baz]]] = [111,[[222],[333]]];
console.log(foo,bar,baz); // 111 222 333

let [head,...foot] = [1,2,3,4]; //。。。扩展运算符 只能出现在解构赋值的末尾,多余的数就会被赋值给扩展运算符
console.log(head,foot); // 1 [2,3,4]     

// 如果解构不成功,变量的值就等于undefined,如下
var [bar3,foo3] = [1000];
console.log(bar3,foo3); // 1000 undefined

// 另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功
let [x,y] = [10000,20000,30000];
console.log(x,y); // 10000 20000

// 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
let [a=1,b=a] = [2];
console.log(a,b); // 2 2

// 对象的解构也可以指定默认值
var {x,y=5} = {x:1};
console.log(x,y); // 1 5
var [a,b=2]=[1]; //1 2
//对象的解构赋值解构不仅可以用于数组,还可以用于对象(json) //对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定; //而对象的属性没有次序,变量必须与属性同名,才能取到正确的值 var {a,b} = {a:'apple',b:'banana'}; console.log(a,b); // apple banana var {b,a} = {a:'apple',b:'banana'}; console.log(a,b); // apple banana // 如果变量名与属性名不一致,必须写成下面这样 let obj = {first:'hello',last:'world'}; // first ---> f,那么此时f就是first,而不是undefined了,有点类似别名的概念 let {first:f,last} = obj; console.log(f,last); // hello world 用相同属性名:变量名 就相当于变量与属性是同名的 //1.也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。 真正被赋值的是后者,而不是前者 //2.v是匹配的模式,n才是变量。真正被赋值的是变量n,而不是模式v。 //注意,采用这种写法时,变量的声明和赋值是一体的 // v ---> n,那么此时n就是vue,而不是undefined了 var {v:n} = {v:'vue',r:'react'}; console.log(n); // vue console.log(v); // Uncaught ReferenceError: v is not defined console.log(r); // Uncaught ReferenceError: r is not defined

 复制数组

// 数组的浅拷贝,引用之间的拷贝,没有实现数组的真正复制
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr2.push(4);
console.log(arr1, arr2); //[1, 2, 3, 4] [1, 2, 3, 4]

// 复制数组深拷贝,传统做法
var arr1 = [1,2,3];
var arr2 = [];
//通过for循环遍历之后将arr1数组的每一项赋值给arr2数组的每一项, 就实现了数组的深拷贝,这时候我再去操作arr2的数组的时候,arr1就不会受影响了
for(var i=0;i<arr1.length;i++){
    arr2[i] = arr1[i];
}
// 数组尾部添加
arr2.push(4);
console.log(arr1,arr2);
//1,2,3,4   1,2,3
// ES6实现的数组的深拷贝方法1 Array.from
var arr1 = [1,2,3];
var arr2 = Array.from(arr1);
// 数组尾部添加
arr2.push(100);
console.log(arr1,arr2); // [1, 2, 3] [1, 2, 3, 100]

// ES6实现的数组的深拷贝方法2
var arr1 = [1,2,3];
// 超引用拷贝数组
var arr2 = [...arr1];
// 数组尾部添加
arr2.push(1000);
console.log(arr1,arr2); // [1, 2, 3] [1, 2, 3, 1000]

function show(...args){
// 此时这个形式参数就是一个数组,我们可以直接push东西进来,如下
args.push(5);
console.log(args);
}
// 调用
show(1,2,3,4); // [1, 2, 3, 4, 5]

 

字符串函数扩展

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

let s = 'Hello world!';

s.startsWith('Hello') // true

s.endsWith('!') // true

s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。

let s = 'Hello world!';

s.startsWith('world', 6) // true

s.endsWith('Hello', 5) // true

s.includes('Hello', 6) // false

 

Map对象

var map = new Map();
// 设置
// map.set(name,value);
map.set('a','apple');
map.set('b','banana');
// 获取
// map.get(name);
console.log(map.get('a') + ' ' + map.get('b'));
// 删除之前map对象
console.log(map);
// 删除
// map.delete(name);
map.delete('a');
// 删除之后map对象
console.log(map);

// 注意for..in是不能循环map对象的,不报错也无任何反应,所以下一代码无任何输出,稍微注意下
for(var name in map){
    console.log(name);
}

// 实体 map对象的循环输出
for(var name of map){
//循环出来的结果就是:a,apple b,banana 循环key,value
console.log(name);
}

//循环出来的结果就是: a,apple b,banana 循环key,value
for(var [key,value] of map.entries()){
    console.log(key,value);
}

//只循环key
for(var key of map.keys()){
    console.log(key);
}

//只循环value
for(var val of map.values()){
    console.log(val);
}

 

for-of循环

什么是 for…of 循环

for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。

//for of一个arr对象
var arr = ['红楼梦','西游记','三国演义','水浒传','火影'];
//只循环key  0 1 2 3 4 输出key值,也就是下标索引
for(var key of arr.keys()){
console.log(key);
}
//只循环value,注意for of默认.values() 所以可以省略不写。直接 var value of arr ,输出 红楼梦,西游记,三国演义,水浒传,火影
for(var value of arr.values()){
console.log(value);
}
//循环key,value
for(var [key,value] of arr.entries()){
console.log(key,value);
}

//for in循环与for of循环的区别
var arr = ['apple','banana','orange','pear'];
for(var i in arr){
// i打印出来的就是arr数组对应的索引
// 0 1 2 3
console.log(i);
}
for(var i of arr){
// i值打印出来的就是我们想要的数组具体的值
// apple banana orange pear
console.log(i);
}

//for of不能循环json
var json = {'a':'apple','b':'banana','c':'orange','d':'pear'};
for(var name in json){
console.log(name);//a b c d
console.log(json.a);// apple
console.log(json['d']);//pear
}
// 注意for..of可以循环arr,但是不可以循环json,会报错,特别注意下
for(var name of json){
Uncaught TypeError: undefined is not a function
console.log(json);
}

 

箭头函数

引入箭头函数有两个方面的作用:更简短的函数并且不绑定this

箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数,不能使用new。

箭头函数的写法  function(){ } 变成 ()=>{ }

1 var a = ()=>{
2   return 1;
3 }

等价于

1 function a(){
2   return 1;
3 }

ES6匿名函数 ()=》{ 函数语句 }

例 

const pipe = x => () => import(`@/pages/${x}`)

等于

const pipe = (x) => {
return () => {
return import(@/pages/${x})
}
}再还原一层

const pipe = (x) => {
return function () {
return import(`@/pages/${x}`)
}
}

该箭头函数返回值还是个箭头函数,这里用到了函数的柯里化

 

如果箭头函数函数体只有一条语句,可以这样写:

1 var fun = ()=>Math.random()*10
2 console.log(fun());

这样子调用这个箭头函数就会直接返回这条语句的值

箭头函数不绑定arguments,

(扩展下argument

arguments 是一个对应于传递给函数的参数的类数组对象。

arguments对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:

arguments[0]
arguments[1]
arguments[2]

参数也可以被设置:

arguments[1] = 'new value';

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array:)

箭头函数不采用arguments 取而代之用rest参数…解决

function A(a){
  console.log(arguments); //[object Arguments] [1, 2, 3]
}

var B = (b)=>{
  console.log(arguments); // arguments里面没值 Arguments [Event, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}

var C = (...c)=>{ //...c即为rest参数
  console.log(c); //[3, 1, 2]
console.log(c[0]) //3
} A(1,2,3); B(2,1,3); C(3,1,2);

 

箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值

var obj = {
  a: 10,
  b: function(){
    console.log(this.a); //输出10
  },
  c: function() {
     return ()=>{
           console.log(this.a); //输出10,捕获了上面obj的this作为自己的this
     }
  }
}
obj.b();
obj.c()();

 

所谓箭头函数的 this 捕获的是所在的上下文,比如下面这个例子:b是一个箭头函数,然后它的 this是指向window,这是为什么呢,因为箭头函数捕获的是obj{}这个对象的环境,然后这个环境的this指向的是window,就相当于上一条的例子:在c方法里面return的那个箭头函数捕获的是c:function(){}这个环境的this,而这个环境的thisobj

var obj = {
  a: 10,
  b: () => {
    console.log(this.a); //undefined
    console.log(this); //window
  },
  c: function() {
    console.log(this.a); //10
    console.log(this); //obj{...}
  }
}
obj.b();
obj.c();

 

对于函数的this指向问题:

1、箭头函数的this永远指向其上下文的 this,任何方法都改变不了其指向,如call(), bind(), apply()

2、普通函数的this指向调用它的那个对象

对象的简洁语法

什么是单体模式
对象只要利用自己的属性完成了自己的任务,那该对象就是承担了责任。除了维持了自身的一致性,该对象无需承担其他任何责任。 如果该对象还承担着其他责任,而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。
//传统对象_单体模式写法 key-value模式
var person = {
    name:'krry',
    age:21,
    showName:function(){
        return this.name;
    },
    showAge:function(){
        return this.age;
    }
};
// 调用
console.log(person.showName()); // krry
console.log(person.showAge()); // 21

//ES6_单体模式写法  不需要写key
var name = 'krry';
var age = 21;
var person = {
    name,
    age,
    showName(){
        return this.name;
    },
    showAge(){
        return this.age;
    }
};
// 调用
console.log(person.showName()); // krry
console.log(person.showAge()); // 21

 

类和继承

1、传统面向对象写法

function Person(name,age){ // 类、构造函数
    this.name = name;
    this.age = age;
}
Person.prototype.showName = function(){
    return this.name;
};
Person.prototype.showAge = function(){
    return this.age;
};
var p1 = new Person('allen',28);
var p2 = new Person('xiaoxiaoyou',101);
console.log(p1.showName()); // allen
console.log(p2.showAge()); // 101
console.log(p1.showName == p2.showName); //true 注意不是调用方法,没有括号,所以才true
console.log(p1.constructor == Person); // true 构造方法相等

 

ES6 class面向对象写法

 

class Person{
    // 构造器
    constructor(name,age){
        this.name = name;
        this.age = age;
//constructor 构造方法 this关键字代表实例对象 }
//定义类的时候,前面不需要加function关键字,方法之间不需要逗号分隔,加了会报错 showName(){
return this.name; } showAge(){ return this.age; } } var p1 = new Person('aaa',18); var p2 = new Person('bbb',20); console.log(p1.name); // aaa console.log(p1.showName()); // aaa console.log(p2.showAge()); // 20 console.log(p1.showAge == p2.showAge); // true console.log(p1.constructor == Person); // true

 

面向对象给class赋值默认值:

class Person{
    // 构造器
    constructor(name='default',age=0){
        this.name = name;
        this.age = age;
    }
    showName(){
        return this.name;
    }
    showAge(){
        return this.age;
    }
}

var p1 = new Person();
console.log(p1.name); // 构造器里面给的默认值 default
console.log(p1.age); // 构造器里面给的默认值 0

 

传统写法原型继承extends

//传统写法原型继承
function Person(name,age){ // 类、构造函数
    this.name = name;
    this.age = age;
}
Person.prototype.showName = function(){
    return this.name;
};
Person.prototype.showAge = function(){
    return this.age;
};
// 工人类
function Worker(name,age){
    // 属性继承过来
    Person.apply(this,arguments);
}
// 原型继承
Worker.prototype = new Person();
var p1 = new Person('allen',28);
var w1 = new Person('worker',1000);
console.log(w1.showName()); // 确实继承过来了 result:worker

 

ES6中面向对象实习类继承

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class Person{
    // 构造器
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    showName(){
        return this.name;
    }
    showAge(){
        return this.age;
    }
}
class Worker extends Person{
//通过extends关键字,继承了Person类的所有属性和方法 constructor(name,age,job
='啦啦啦'){ // super 这里表示父类的构造函数,用来新建父类的this对象。 super(name,age); this.job = job; } showJob(){ return this.job; } } var p1 = new Person('aaa',18); var w1 = new Person('www',36); var w2 = new Worker('wwwwwwww',90); console.log(w1.showName()); // www console.log(w2.showJob()); // 默认给的值 ‘啦啦啦’

 super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

constructor(...args) {
  super(...args);
}

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

es6如果键 值一样 可以缩写成一个词

 

Promise

在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。

就是用同步的方式去写异步代码。

//Promise对象 ---> 用来传递异步操作过来的数据的
//Pending(等待、处理中) ---> Resolve(完成,fullFilled)   ---> Reject(拒绝,失败)
//这里只是定义,还没开始执行
var p1 = new Promise(function(resolve,reject){
    resolve(1); // 成功了,返回一个promise对象1
    // reject(2); // 失败了
});

// 接收成功和失败的数据,通过then来传递
// then也是返回一个promise对象,会继续往下传递数据,传递给下一个then
p1.then(function(value){
    // resolve
    console.log(value); //执行打印1
    return value + 1; // 1
    alert(`成功了:${value}`);
},function(value){
    // reject
    alert(`失败了:${value}`);
}).then(function(value){
    console.log(value); // 2
});

//catch捕获异常错误
var p1 = new Promise(function(resolve,reject){
    resolve('成功了'); //返回一个promise对象“成功了”
});
//then也是返回一个promise对象,会继续往下传递数据
p1.then(function(value){
    console.log(value); //打印“成功了”
    // throw是用来抛错误的
    throw '发生了点小意外';
}).catch(function(e){
    // catch用来捕获这个错误的 ---> 追踪
    console.log(e);
});

//all ---> 全部,用于将多个promise对象,组合,包装成
//Promise.all([p1,p2,p3,...]); 所有的promise对象,都正确,才走成功
//否则,只要有一个错误,就走失败
var p1 = Promise.resolve(1);
var p2 = Promise.reject(0);
Promise.all([true,p1,p2]).then(function(obj){
    console.log(`成功了:${obj}`);
},function(obj){
    console.log(`失败了:${obj}`);
});

// race ---> 返回的也是一个promise对象
//最先执行的的promise结果,哪个最快我用哪个,所以下面打印的是one
var p1 = new Promise(function(resolve,reject){
    setTimeout(resolve,50,'one');
});
var p2 = new Promise(function(resolve,reject){
    setTimeout(resolve,100,'two');
});
Promise.race([p1,p2]).then(function(val){
    console.log(val);
});

//resolve ---> 生成一个成功的promise对象
//语法规则:Promise.resolve(val); // 普通值
// Promise.resolve(arr); // 数组之类
//Promise.resolve(promise); // 传递另一个promise对象
//传递普通值
Promise.resolve('success').then(function(val){
    // 注意resolve,走得是这里
    console.log(val); // success
},function(err){
    console.log("err:"+ err);
});
//传递数组
Promise.resolve([1,2,3]).then(function(val){
    // 注意resolve,走得是这里
    console.log(val); // [1,2,3]
},function(err){
    console.log(err);
});
//传递一个promise对象
var p1 = Promise.resolve(520);
var p2 = Promise.resolve(p1);
p2.then(function(val){
    //从p1那边传递过来的
    console.log(val); // 520
});

 

经典面试题

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

 

首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。

然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。

然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。

因此,应当先输出 5,然后再输出 4 。

最后在到下一个 tick,就是 1 。

“2 3 5 4 1”

Generator、yield

生成器( generator)是能返回一个迭代器的函数。生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。

这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。

当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。

看个例子:

// 生成器
    function *createIterator() {
        yield 1;
        yield 2;
        yield 3;
    }
    
    // 生成器能像正规函数那样被调用,但会返回一个迭代器
    let iterator = createIterator();
    
    console.log(iterator.next().value); // 1
    console.log(iterator.next().value); // 2
    console.log(iterator.next().value); // 3
    console.log(iterator.next().value); // undefined  因为generator已经停止提供值

 

总结

//Generator ---> 生成器就是一个函数
//特点:
//1.函数名前面带一个*,和普通函数做区分
//2.内部使用yield语句
//调用方式,如下var res = show(); 与普通函数一样
//value指的是generator函数内容yield定义的值,done:false表示还没遍历完
//直接找到返回值return了,那么此时done才会为true
//console.log(res.next());{value:'值1',done:false}
function* show(){
    yield 'Hello';
    yield 'World';
    yield 'ES6';
    return 'xx';
}
var res = show();
console.log(res.next()); // {value: "Hello", done: false}
console.log(res.next()); // {value: "World", done: false}
console.log(res.next()); // {value: "ES6", done: false}
console.log(res.next()); // {value: "allen", done: true}
// 已经找到return返回值了,继续下去就没有意义了
// console.log(res.next()); // {value: "undefined", done: true}

//yield本身没有返回值,或者可以说每次给你返回的是undefined
function* show(){
    var a = yield 'Hello';
    return a;
}
var res = show();
console.log(res.next()); // {value: "Hello", done: false}
console.log(res.next()); // {value: "undefined", done: true}

//next方法是可以带参数的,死循环的generator函数
function* fn(){
    for(var i=0;true;i++){
        // 如果里面传了一个值,那么它会把这个参数赋给最近的一个yield
        var a = yield i;
        if(a) i = -1;
    }
}
var d = fn();
console.log(d.next()); // {value: 0, done: false}
console.log(d.next()); // {value: 1, done: false}
console.log(d.next()); // {value: 2, done: false}
// 如果里面传了一个值,那么它会把这个参数赋最近的一个yield
console.log(d.next(true)); // {value: 0, done: false}
console.log(d.next()); // {value: 1, done: false}
console.log(d.next()); // {value: 2, done: false}
console.log(d.next()); // {value: 3, done: false}

// for..0f循环generator函数
function* fn(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
}
//for..0f循环generator函数,可以取值
for(let val of fn()){
document.write(val); // 12345
}

// 对象里使用generator函数的特殊写法,注意下
var json = {
    *show(){
        yield 'a';
        yield 'b';
        return 'c';
    }
};
var res = json.show();
console.log(res.next()); // {value: "a", done: false}
console.log(res.next()); // {value: "b", done: false}
console.log(res.next()); // {value: "c", done: true}

 

自动调用生成器并启动迭代器的方法:

function run(taskDef) { //taskDef即一个生成器函数

    // 创建迭代器,让它在别处可用
    let task = taskDef();

    // 启动任务
    let result = task.next();

    // 递归使用函数来保持对 next() 的调用
    function step() {

        // 如果还有更多要做的
        if (!result.done) {
            console.log(result.value); //这里就执行该做的事
            result = task.next();
            step();
        }
    }

    // 开始处理过程
    step();

}
//生成器
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
//启动
run(createIterator);

 

async函数

含义

ES7提供了async函数,使得异步操作变得更加方便。async函数是什么?一句话,async函数就是Generator函数的语法糖。

前文有一个Generator函数,依次读取两个文件。

var fs = require('fs');

var readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

写成async函数,就是下面这样。

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

var result = asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像Generator函数,需要调用next方法,或者用co模块,才能得到真正执行,得到最后结果。

(2)更好的语义。asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。 co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

(4)返回值是Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。

语法

async函数的语法规则总体上比较简单,难点是错误处理机制。

(1)async函数返回一个Promise对象。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到。

async函数内部抛出错误,会导致返回的Promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了

(2)async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

下面是一个例子。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

(3)正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

上面代码中,await命令的参数是数值123,它被转成Promise对象,并立即resolve

await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。

只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject

为了避免这个问题,可以将第一个await放在try...catch结构里面,这样第二个await就会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一种方法是await后面的Promise对象再跟一个catch方面,处理前面可能出现的错误。

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

如果有多个await命令,可以统一放在try...catch结构中。

async function main() {
  try {
    var val1 = await firstStep();
    var val2 = await secondStep(val1);
    var val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

(4)如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了

上面代码中,async函数f执行后,await后面的Promise对象会抛出一个错误对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,可以参考后文的“async函数的实现”。

防止出错的方法,也是将其放在try...catch代码块之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

async函数的实现

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args){
  // ...
}

// 等同于

function fn(args){
  return spawn(function*() {
    // ...
  });
}

所有的async函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出spawn函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

async函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器Babelregenerator都已经支持,转码后就能使用。

async 函数的用法

async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

下面是一个例子。

async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

下面的例子,指定多少毫秒后输出一个值。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

上面代码指定50毫秒以后,输出"hello world"。

Async函数有多种使用形式。

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

注意点

第一点,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  };
}

第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

如果确实希望多个请求并发执行,可以使用Promise.all方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

ES6将await增加为保留字。使用这个词作为标识符,在ES5是合法的,在ES6将抛出SyntaxError。

与Promise、Generator的比较

我们通过一个例子,来看Async函数与Promise、Generator函数的区别。

假定某个DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。

首先是Promise的写法。

function chainAnimationsPromise(elem, animations) {

  // 变量ret用来保存上一个动画的返回值
  var ret = null;

  // 新建一个空的Promise
  var p = Promise.resolve();

  // 使用then方法,添加所有动画
  for(var anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一个部署了错误捕捉机制的Promise
  return p.catch(function(e) {
    /* 忽略错误,继续执行 */
  }).then(function() {
    return ret;
  });

}

虽然Promise的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是Promise的API(then、catch等等),操作本身的语义反而不容易看出来。

接着是Generator函数的写法。

function chainAnimationsGenerator(elem, animations) {

  return spawn(function*() {
    var ret = null;
    try {
      for(var anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* 忽略错误,继续执行 */
    }
    return ret;
  });

}

上面代码使用Generator函数遍历了每个动画,语义比Promise写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行Generator函数,上面代码的spawn函数就是自动执行器,它返回一个Promise对象,而且必须保证yield语句后面的表达式,必须返回一个Promise。

最后是Async函数的写法。

async function chainAnimationsAsync(elem, animations) {
  var ret = null;
  try {
    for(var anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}

可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将Generator写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用Generator写法,自动执行器需要用户自己提供。

实例:按顺序完成异步操作

实际开发中,经常遇到一组异步操作,需要按照顺序完成。比如,依次远程读取一组URL,然后按照读取的顺序输出结果。

Promise 的写法如下。

function logInOrder(urls) {
  // 远程读取所有URL
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // 按次序输出
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

上面代码使用fetch方法,同时远程读取一组URL。每个fetch操作都返回一个Promise对象,放入textPromises数组。然后,reduce方法依次处理每个Promise对象,然后使用then,将所有Promise对象连起来,因此就可以依次输出结果。

这种写法不太直观,可读性比较差。下面是async函数实现。

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}

上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个URL返回结果,才会去读取下一个URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

上面代码中,虽然map方法的参数是async函数,但它是并发执行的,因为只有async函数内部是继发执行,外部不受影响。后面的for..of循环内部使用了await,因此实现了按顺序输出。

个人总结重点

let声明块级变量 同一个作用域内部允许重复,不存在预解析  const用来声明常量 不能修改 一开始必须赋值

字符串拼接 ` ` 里面${ }里面放变量或者函数或者表达式   ES6赋值 var [a,b,c]=[11,22,33]  var [a,...b]=[1,22,33] ...扩展运算符

只能出现在末尾 多余的数会被赋值给扩展运算符。还可以运用于对象   var {a,b} = {a:'apple',b:'banana'};

es6 数组深拷贝 var arr2=Array.from(arr1); var arr2=[...arr1];  Map对象 var map1=new Map(), map1.set map1.get map1.delete 设置获取删除属性

arguments 非箭头函数的局部变量 类似数组,包含着传递给函数的每个参数

for of循环  允许遍历数组,字符串,Maps(映射),Sets(集合)for(var value of arr.values()) 或者for(var key of arr.keys) for(var [key,value] of arr.entries

箭头函数 更加简短的函数 不绑定this,但是会捕获上下文的this值    ()=>{函数语句}

简洁语法  


class Person{
    // 构造器赋值默认值
    constructor(name='default',age=0){
        this.name = name;
        this.age = age;
    }
    showName(){
        return this.name;
    }
    showAge(){
        return this.age;
    }
} 
var p1 = new Person();
console.log(p1.name); // 构造器里面给的默认值 default
console.log(p1.age); // 构造器里面给的默认值 0

类的继承
class Worker extends Person{ constructor(name,age,job='啦啦啦'){ // 继承超父类的属性 super(name,age); this.job = job; } showJob(){ return this.job; } } Worker对象继承了Person对象 属性和方法

 

。。。

posted @ 2020-06-11 15:56  Ren小白  阅读(100)  评论(0)    收藏  举报
levels of contents