序:

1.用let const 声明变量。

2.解构赋值:

用途:a.交换变量的值;  b.从函数返回多个值;   c.函数参数的定义及默认值;   d.提取JSON数据;   e.遍历Map;   f.输入模块。

3.字符串的扩展:

a.完善以前超出范围的字符处理;   b.可以用for...of循环;    c.includes(),startWith(),endsWith(),repeat(),

padStart(),padEnd()等api;   d.模板字符串;  e.标签模板(是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。作用1:过滤html字符串,防止用户恶意输入;2.多语言转换。)

4.正则的扩展:

u 修饰符(完善以前超出范围的字符处理)、y修饰符(粘连;类似g修饰符,确保匹配必须从剩余的第一个位置开始。)等。

5.数值的扩展:

Number.isNaN()  Number.isFinite()(只对数值有效,不转换) Number.isInteger()(是否为整数) Number.EPSILON()(设置误差范围 比如0.1+0.2不等于0.3) 。Math.trunc()(去除小数部分);Math.sign()(判断一个数是正负还是0或NAN);Math.cbrt()(立方根); Math.clz32(); Math.imul();Math.fround();Math.hypot()(所有参数的平方和的平方根) 还有一些对数方法及指数运算符(**)等。

6.函数的扩展:

a参数可以设置默认值;  b.rest参数(...变量名,是一个数组。),用于获取函数的多余参数,可以取代arguments对象。  c.箭头函数(this是定义时的指向,不是运行时指向。无arguments对象,不能new,不能yield);  d.双冒号运算符(::用于绑定this);   e.尾调用和尾递归的优化(减少调用帧,节省内存。可以用循环代替递归)。

7.数组的扩展:

a.扩展运算符(...)是rest参数的逆运算,将数组转为逗号分隔的参数序列,主要用于函数调用,可替代apply方法,用途很广;也可以将某些数据结构转为数组(背后调用的是iterator接口);   b.Array.from:将类数组对象和可遍历对象转为真正的数组。(只要有length属性都可以转换);  c.Array.of():将一组值,转换为数组;    d.copyWithin()(将指定位置的成员复制到其他位置,会覆盖,然后返回当前数组);   e.find()和findIndex()(参数是一个回调函数,返回第一个找到的成员(下标)) ;     f.fill():填充数组,用于初始化数组;   g.keys(),values() entries(),:分别遍历下标,键值,键值对。  h.includes():类似字符串的includes。  i.数组的空位(尽量避免空位)

8.对象的扩展:

a.简洁表达法;     b.属性名表达式([property])    c.Object.is()(与===的区别为is的+0不等于 -0,NaN等于自身);    d.Object.assign()(用于合并对象,同名属性会覆盖,属于浅拷贝,即拷贝的是引用。),可以为对象添加属性、方法、克隆对象、合并对象、为属性指定默认值等。   e.Object.getOwnPropertyDescriptor(s)获取属性的描述对象

f.属性的遍历(for...in(遍历对象自身和继承的可枚举属性)   Object.keys(obj)(返回一个数组,包括对象自身(不含继承的)可枚举属性)   Object.getOwnPropertyNames(obj)  (返回一个数组,包含对象自身的所有属性,包括不可枚举的)Object.getOwnPropertySymbols(obj)   Reflect.ownKeys(obj)(包含所有键名,包含Symbol,也无论是否可枚举)

g.__proto__属性的替代:Object.setPrototypeOf()、Object.getPrototypeOf()、Object.create();

h.super()关键字:指向对象的原型对象。

i.Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

Object.values():方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

Object.entries():方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。可用于遍历对象的属性,和讲对象转为Map结构。

j. 对象的扩展运算符;     k.    ?. (简化判断是否存在的写法)。

Symbol:

独一无二的值,通过Symbol函数生成;如Symbol.iterator属性

Set和Map结构:

1.Set:

a.类似于数组,但是成员的值都是唯一的,没有重复的值,可以去重。   b.属性:constructor,size;

c.方法:add(value),delete(value),has(value),clear();     d.遍历:keys(),values(),entries(),forEach();

WeakSet适合临时存放一组对象,不适合引用,因为会随时消失;

2.Map:

a.提供(值-值对结构),可以被for...of遍历;   b.属性和方法:size,   set(key,value), get(key), has(key) ,delete(key),

clear();     c.遍历:同set;

WeakMap 可以将dom节点作为键名,有助于防止内存泄露。

Proxy:可以在目标对象之前设置一层拦截,对外界的访问进行过滤和改写。

Reflect:未来从Reflect身上拿语言内部的方法并修正细节。如Object.defineProperty等

Promise对象:

异步回调解决的一种方案,Promise实例生成后,可以用then方法分别指定resolved和rejected状态的回调函数。Promise.all()将多个Promise实例包装成一个实例。都完成才完成,一个失败即失败;Promise.race()只要有一个实例率先改变状态,总实例就改变状态。Promise也可以结合Generator函数使用。

Iterator和for...of: 

a.可以为数据(Object Array Map Set)设置Iterator接口(Symbol.iterator),可以供for...of遍历。

b.调用Iterator接口的场合:解构,...运算符,yield* 数组,字符串,for...of,Generator,Set,Map,类数组也可for...of;

普通对象需要部署接口或者用Object.keys()或Generator函数重新包装。

c.for...of的优势:1.没有for..in的一些缺点(字符串下标,牵扯原型链等);2.与forEach比可以与break,continue,return配合;3.提供遍历数据统一接口。

Generator:

a.异步编程解决方案之一,状态机及遍历器对象生成函数。调用后不执行,返回指向内部状态的遍历器对象。yield表达式是暂停执行的标记,调用next方法,移动指针至下一步,直到结束或return。

b.next方法可以带参数,该参数被当做上一个yield表达式的返回值(否则yield表达式为undefined)

c.可以将generator函数改造成普通构造函数。

d.generator的异步应用:需配合Thunkify或co模块。

e.generator函数的语法糖升级版:async函数:将yield改成await,*号改成async,并内置执行器,返回promise对象。

Class:类。

继承使用extends关键字。在调用super()后才可以使用this关键字。

Decorator修饰器:

用来修改类的行为。@  core-decorator第三方库提供常见修饰器。(@autobind(this绑定原始对象) @readonly(属性方法不可写) @override(检查子类方法是否正确覆盖父类的同名方法。)等)

Module语法:模块化 import导入 export(default)导出。

CommonJS模块是运行时加载,ES6模块是编译时输出接口;

CommonJS模块输出的是值的拷贝,ES6模块输出的是值的引用。

以下是详细摘要及总结:

---------------------------------------------------------------------------------------------------------------------------------------------------------------

1.let const声明变量。

1.let:不存在变量提升,变量需要声明后使用,不允许重复声明,适用for循环等。

2.const:const保证的不是变量的值不得改动,而是变量指向的内存地址不得改动,(例如可以为对象添加属性,不能将对象指向别的对象。)适用于模块内声明变量等。

3.es6声明变量的六种方法 var function let const import class.

2.解构赋值。(一些栗子)

 1 let [a, [b], d] = [1, [2, 3], 4];
 2 a // 1
 3 b // 2
 4 d // 4
 5
 6 let [x = 1] = [undefined];
 7 x // 1
 8
 9 let [x = 1] = [null];
10 x // null
11
12
13 //用变量使用Math的方法
14 let { log, sin, cos } = Math;
15
16
17 //由于数组本质是特殊的对象,所以可以对数组进行对象属性的解构
18 let arr = [1, 2, 3];
19 let {0 : first, [arr.length - 1] : last} = arr;
20 first // 1
21 last // 3
22
23
24 //函数参数的解构也可以使用默认值。
25 function move({x = 0, y = 0} = {}) {
26   return [x, y];
27 }
28
29 move({x: 3, y: 8}); // [3, 8]
30 move({x: 3}); // [3, 0]
31 move({}); // [0, 0]
32 move(); // [0, 0
33
34 //以下是一些用途:
35 //交换数值
36 let x = 1;
37 let y = 2;
38
39 [x, y] = [y, x];
40
41
42 // 返回一个数组
43
44 function example() {
45   return [1, 2, 3];
46 }
47 let [a, b, c] = example();
48
49 // 返回一个对象
50
51 function example() {
52   return {
53     foo: 1,
54     bar: 2
55   };
56 }
57 let { foo, bar } = example();
58
59 //将一组参数与变量名对应起来。
60 function f([x, y, z]) { ... }
61 f([1, 2, 3]);
62
63 // 参数是一组无次序的值
64 function f({x, y, z}) { ... }
65 f({z: 3, y: 2, x: 1});
66
67 //提取json数据很有效
68 let jsonData = {
69   id: 42,
70   status: "OK",
71   data: [867, 5309]
72 };
73
74 let { id, status, data: number } = jsonData;
75
76 console.log(id, status, number);
77 // 42, "OK", [867, 5309]
78
79 //输入模块的指定方法
80 const { SourceMapConsumer, SourceNode } = require("source-map");

3.字符串的扩展。

1.可以使用for...of遍历字符串  ;

2.新方法:includes(),startsWith(),endsWith(),repeat()        padstart(),padEnd() (补全字符串);

3.模板字符串  ` ......${a}......`   (非常实用);

4.函数的扩展。

1.函数可以使用默认参数,自动声明,不能用let或const再次声明,参数默认值是惰性求值(每次调用函数都会重新计算).

//若参数中没有={}则foo()会报错
function foo({x, y = 5}={}) {console.log(x, y);
}foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // undefined 5

指定默认值后,函数的length属性返回没有指定默认值的参数的个数。

可以利用参数默认值,指定某个参数不能省略,如果省略就抛出错误,如下:

function throwIfMissing() {throw new Error('Missing parameter');
}function foo(mustBeProvided = throwIfMissing()) {return mustBeProvided;
}foo()
// Error: Missing parameter

参数的默认值不是在定义时执行,而是在运行时执行,若将默认值设为undefined,表明这个参数可以省略。

2.用rest参数(...)取代arguments对象.

function add(...values) {let sum = 0;for (var val of values) {sum += val;}return sum;
}add(2, 5, 3) // 10// arguments变量的写法
function sortNumbers() {return Array.prototype.slice.call(arguments).sort();
}// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

3.箭头函数.

var f = () => 5;
// 等同于
var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {return num1 + num2;
};var sum = (num1, num2) => { return num1 + num2; }// 报错
let getTempItem = id => { id: id, name: "Temp" };// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });//只有一行且不需要返回值时可以省略大括号
let fn = () => void doesNotReturn();//表达更简洁
const isEven = n => n % 2 == 0;
const square = n => n * n;// 正常函数写法
var result = values.sort(function (a, b) {return a - b;
});// 箭头函数写法
var result = values.sort((a, b) => a - b);// 正常函数写法
[1,2,3].map(function (x) {return x * x;
});// 箭头函数写法
[1,2,3].map(x => x * x);

注意点:

函数体内的this对象是定义时所在的对象,而不是使用时所在的对象。(不能使用new),没有arguments对象(用rest...代替),

不能用作Generator(生成器)函数.

//this区别栗子  箭头函数的this绑定Timer
function Timer() {this.s1 = 0;this.s2 = 0;// 箭头函数setInterval(() => this.s1++, 1000);// 普通函数setInterval(function () {this.s2++;}, 1000);
}var timer = new Timer();setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0var handler = {id: '123456',init: function() {document.addEventListener('click',event => this.doSomething(event.type), false);},doSomething: function(type) {console.log('Handling ' + type  + ' for ' + this.id);}
};
/*上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。*/

深究:其实箭头函数没有自己的this.所以使用的是外层的this,所以不能用call apply bind方法.(可以用::替代)

4.尾递归优化:将递归改写为循环;

5.尾调用优化:只保留内层函数的调用帧(简写)

5.数组的扩展。

1.扩展运算符(...) 是rest参数的逆运算。将数组转为逗号分隔的参数序列。

即:三个点(...)在函数参数里使用时,代表一个数组;在函数调用的()里使用时,代表参数序列。

可以替代数组的apply方法,如下:

// ES5 的写法
function f(x, y, z) {// ...
}
var args = [0, 1, 2];
f.apply(null, args);// ES6的写法
function f(x, y, z) {// ...
}
let args = [0, 1, 2];
f(...args);

扩展运算符的应用:

a,复制数组 例如:   let a2=[...a1] ;

b.合并数组 例如:  [...arr1,...arr2,...arr3]  类似es5的concat方法;

c.与解构赋值结合  例如:[a,...rest]=list;

d.可以将字符串转为真正的数组 例如:[...'hello']  //["h","e","l","l","o"];

e.将实现了Iterator的类数组转为数组  例如[...nodelist] (例外:若类数组无Iterator接口可以用Array.from转化成数组,如下:)

let arrayLike = {'0': 'a','1': 'b','2': 'c',length: 3
};// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
//可以使用Array.from(arrayLike)

f.可以用于Map、Set结构和Generator函数

2.Array.from() :将类数组对象和可遍历的对象转为数组 ,只要有length属性即可(如Array.from{length:3} //[undefined*3])

如下:(es5的替代方法为Array.prototype.slice)

// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {console.log(p);
});// arguments对象
function foo() {var args = Array.from(arguments);// ...
}

Array.from还接受第二个参数,可以对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

扩展应用:

Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

以上代码的第一个参数指定了第二个参数运行的次数,很灵活。

3.Array.of():可以替代Array()方法,行为更统一。   (实现为return [].slice.call(arguments);

4.copyWithin() :将指定位置的成员复制到其他位置(会覆盖),然后返回当前数组。

5.数组实例的find()和findIndex()方法:

find():找出第一个符合条件的数组成员(第一个为true的成员),若没有,返回undefined;

[1, 5, 10, 15].find(function(value, index, arr) {return value > 9;
}) // 10

findIndex():找出第一个符合条件的数组成员的位置,若没有,返回-1;

这两个方法都可以接受第二个参数,绑定回调函数的this对象。

6.fill()方法:填充数组,如下:

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
//三个参数分别是填充的值,开始位置和结束位置

7.数组实例的entries(),keys()和values():用于for...of循环遍历。

8.includes():类似字符串的includes方法,比indexOf()更语义化一些,也不会对NaN造成误判。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

9:数组的空位:es6将空位转为undefined , es5则比较混乱。

6.对象的扩展。

1.属性/方法名的简写:注意:简写的属性/方法名是字符串,不属于关键字。(可以用class()等)

2.属性名表达式:可以在对象中将表达式放在方括号里,注意:如果属性名表达式是一个对象,会将对象转为字符串[object object],需要注意。如下:

const keyA = {a: 1};
const keyB = {b: 2};const myObject = {[keyA]: 'valueA',[keyB]: 'valueB'
};myObject // Object {[object Object]: "valueB"}

3.Object.is()   类似===    (不同之处 +0不等于-0,NaN等于自身)

4.Object.assign()  拷贝(属性相同会覆盖,只拷贝可以枚举的属性,不拷贝继承属性)

const v1 = 'abc';
const v2 = true;
const v3 = 10;const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

以上,除了字符串以数组形式拷贝入目标对象,其他无效果。

注意点:

a.Object.assign()是浅拷贝,如果源对象某个属性的值是对象,那么拷贝得到的是它的引用,互相影响;

b.同名属性的替换;

c.若用来处理数组,会把数组视为对象;

常见用途:

a.为对象加属性/方法,如下:

//添加属性class Point {constructor(x, y) {Object.assign(this, {x, y});}
}
//添加方法
Object.assign(SomeClass.prototype, {someMethod(arg1, arg2) {···},anotherMethod() {···}
});// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {···
};
SomeClass.prototype.anotherMethod = function () {···
};

b.克隆对象,如下:

function clone(origin) {return Object.assign({}, origin);
}//若想保持继承链,可以如下:
function clone(origin) {let originProto = Object.getPrototypeOf(origin);return Object.assign(Object.create(originProto), origin);
}

c.合并对象,如下:

const merge =(...sources) => Object.assign({}, ...sources);

5.属性的可枚举性和遍历:可以使用Object.getOwnPropertyDescriptor(obj,'name')获取属性的描述对象。

有四个操作会忽略enumerable为false的属性:for ..in  Object.keys()   JSON.stringify()  Object.assign()。

其中只有for...in会返回继承的属性。可以用Object.keys()代替for...in;

6.Object.getOwnPropertyDescriptors返回指定对象所有自身属性(非继承属性)的描述对象;

7.__proto__:内部属性,可以用Object.setPrototypeOf()   Object.getPrototypeOf()   Object.create()代替。

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);proto.y = 20;
proto.z = 40;obj.x // 10
obj.y // 20
obj.z // 40

function Rectangle() {// ...
}const rec = new Rectangle();Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

8.super关键字(注意:目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。)

它指向当前对象的原型对象。

9.Object.keys()  Object.values() Object.entries()

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };for (let key of keys(obj)) {console.log(key); // 'a', 'b', 'c'
}for (let value of values(obj)) {console.log(value); // 1, 2, 3
}for (let [key, value] of entries(obj)) {console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

10.对象的扩展运算符

a.解构赋值:浅拷贝(若有负荷类型的值则拷贝引用,会互相影响,且不能复制继承自原型的属性(普通解构赋值可以继承到))

b.扩展运算符可以合并两个对象

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

11.Null传导运算符:判断对象是否存在时的简写。  如:message?.body?.user?.firstname||'default'

 7.新的原始数据类型Symbol

表示独一无二的值,可以用Object.getOwnPropertySymbols获取到,不会被普通方法遍历到,所以可以定义一些内部的变量等。

8.Set和Map结构

可以用[...new Set(array)]去除数组的重复成员(或者Array.from(new Set(array)))

Set结构的属性:.size  .constructor  方法:add(value) delete(value) has(value) clear()

// 对象的写法
const properties = {'width': 1,'height': 1
};if (properties[someName]) {// do something
}// Set的写法
const properties = new Set();properties.add('width');
properties.add('height');if (properties.has(someName)) {// do something
}

Set的遍历操作:keys() values() entries() forEach()  注意点:Set的遍历顺序就是插入顺序。

Map结构是一种值-值对的数据结构   属性/方法:size   set(key,value)  get(key) has(key) delete(key) clear()

WeakSet/WeakMap使用场景 :在DOM元素上添加数据时,可以不用手动删除引用(避免内存泄漏)。如下:

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [[e1, 'foo 元素'],[e2, 'bar 元素'],
];
// 不需要 e1 和 e2 的时候
// 必须手动删除引用
arr [0] = null;
arr [1] = null;//若使用WeakMap()可以不手动释放对象。

9.Promise对象(用来传递异步操作的数据(消息))

缺点:

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

如果某些事件不断地反复发生,一般来说,使用Stream模式是比部署Promise更好的选择。

基本用法:

a.Promise 新建后立即执行,然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行。

下面是一个用Promise对象实现的 Ajax 操作的例子。

const getJSON = function(url) {const promise = new Promise(function(resolve, reject){const handler = function() {if (this.readyState !== 4) {return;}if (this.status === 200) {resolve(this.response);} else {reject(new Error(this.statusText));}};const client = new XMLHttpRequest();client.open("GET", url);client.onreadystatechange = handler;client.responseType = "json";client.setRequestHeader("Accept", "application/json");client.send();});return promise;
};getJSON("/posts.json").then(function(json) {console.log('Contents: ' + json);
}, function(error) {console.error('出错了', error);
});

b.resolve函数的参数可以是另一个Promise实力,如下:

const p1 = new Promise(function (resolve, reject) {setTimeout(() => reject(new Error('fail')), 3000)
})const p2 = new Promise(function (resolve, reject) {setTimeout(() => resolve(p1), 1000)
})p2.then(result => console.log(result)).catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个 Promise,3 秒之后变为rejectedp2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对p1。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

c.良好习惯:在resolve和reject前加上return,后续操作放在then方法里。

Promise.prototype.then():

getJSON("/post/1.json").then(post => getJSON(post.commentURL)//相当于es5的return getJSON(...)
).then(comments => console.log("resolved: ", comments),//resolve时err => console.log("rejected: ", err)//reject时
);

Promise.prototype.catch():

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

const someAsyncThing = function() {return new Promise(function(resolve, reject) {// 下面一行会报错,因为x没有声明resolve(x + 2);});
};someAsyncThing().then(function() {return someOtherAsyncThing();
}).catch(function(error) {console.log('oh no', error);// 下面一行会报错,因为 y 没有声明y + 2;
}).then(function() {console.log('carry on');
});
// oh no [ReferenceError: x is not defined]

上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层.

可以改写如下:

someAsyncThing().then(function() {return someOtherAsyncThing();
}).catch(function(error) {console.log('oh no', error);// 下面一行会报错,因为y没有声明y + 2;
}).catch(function(error) {console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

Promise.all():

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。(const p = Promise.all([p1, p2, p3]);

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。例子如下:

const databasePromise = connectDatabase();const booksPromise = databasePromise.then(findAllBooks);const userPromise = databasePromise.then(getCurrentUser);Promise.all([booksPromise,userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

Promise.race():

const p = Promise.race([p1, p2, p3]);只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。例子:
const p = Promise.race([fetch('/resource-that-may-take-a-while'),new Promise(function (resolve, reject) {setTimeout(() => reject(new Error('request timeout')), 5000)})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

Promise.resolve():将现有对象转为 Promise 对象.当参数是一个thenable对象时,如下:
let thenable = {then: function(resolve, reject) {resolve(42);}
};let p1 = Promise.resolve(thenable);
p1.then(function(value) {console.log(value);  // 42
});

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

注意点:立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {console.log('three');
}, 0);Promise.resolve().then(function () {console.log('two');
});console.log('one');// one
// two
// three

Promise.reject():

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))p.then(null, function (s) {console.log(s)
});
// 出错了

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

可以自己部署的方法:done():用于代码最后捕捉错误。实现如下:
Promise.prototype.done = function (onFulfilled, onRejected) {this.then(onFulfilled, onRejected).catch(function (reason) {// 抛出一个全局错误setTimeout(() => { throw reason }, 0);});
};

finally():finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

server.listen(0).then(function () {// run test
  }).finally(server.stop);
//最后用finally关掉服务器,无论结果如何

实现如下:

Promise.prototype.finally = function (callback) {let P = this.constructor;return this.then(value  => P.resolve(callback()).then(() => value),reason => P.resolve(callback()).then(() => { throw reason }));
};

Promise.try():让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API

第一种写法:

(async () => f())()
.then(...)
.catch(...)

第二种写法:

const f = () => console.log('now');
(() => new Promise(resolve => resolve(f()))
)();
console.log('next');
// now
// next

用Promise.try(),如下:

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有很多好处,其中一点就是可以更好地管理异常。

10.Iterator(遍历器)和for...of

1.a.任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。主要供for...of消费。

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。

例如:
const obj = {[Symbol.iterator] : function () {return {next: function () {return {value: 1,done: true};}};}
};

b.原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象
一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署(也可以修改)遍历器生成方法。(或者使用Map结构)

let it=something[Symbol.iterator]() it.next()...

c.遍历器对象也可以部署return()和throw()方法,如下:
function readLinesSync(file) {return {next() {return { done: false };},return() {file.close();return { done: true };},};
}
// 情况一
for (let line of readLinesSync(fileName)) {console.log(line);break;
}// 情况二
for (let line of readLinesSync(fileName)) {console.log(line);continue;
}// 情况三
for (let line of readLinesSync(fileName)) {console.log(line);throw new Error();
}
//3种情况都会触发return

上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二输出所有行以后,执行return方法,关闭该文件;情况三会在执行return方法关闭文件之后,再抛出错误。注意,return方法必须返回一个对象,这是 Generator 规格决定的.

2.for...of(内部调用Symbol.iterator方法)for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、 Generator 对象,以及字符串。for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法

对象:对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。解决方案:
for (var key of Object.keys(someObject)) {console.log(key + ': ' + someObject[key]);
}

与其他遍历语法的比较:

for循环比较麻烦。forEach方法无法中途跳出。break return都无效。for...in的缺点:

  • 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for...in循环会以任意顺序遍历键名。
  • for...in循环主要是为遍历对象而设计的,不适用于遍历数组。

for...of可以与break continue return配合使用。

 11.Generator函数
1.a.Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(遍历器对象)

每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending';
}var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

b.yield表达式

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

c.与Iterator接口的关系:

可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {yield 1;yield 2;yield 3;
};[...myIterable] // [1, 2, 3]

2、next方法的参数

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* f() {for(var i = 0; true; i++) {var reset = yield i;if(reset) { i = -1; }}
}var g = f();g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

function* foo(x) {var y = 2 * (yield (x + 1));var z = yield (y / 3);return (x + y + z);
}var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

3.for...of循环

for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。(不包括return的值)

原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。如下:

function* objectEntries() {let propKeys = Object.keys(this);for (let propKey of propKeys) {yield [propKey, this[propKey]];}
}let jane = { first: 'Jane', last: 'Doe' };jane[Symbol.iterator] = objectEntries;for (let [key, value] of jane) {console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

function* numbers () {yield 1yield 2return 3yield 4
}// 扩展运算符
[...numbers()] // [1, 2]// Array.from 方法
Array.from(numbers()) // [1, 2]// 解构赋值
let [x, y] = numbers();
x // 1
y // 2// for...of 循环
for (let n of numbers()) {console.log(n)
}
// 1
// 2

4.Generator.prototype.throw():

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。

不要混淆遍历器对象的throw方法和全局的throw命令。上面代码的错误,是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。

var g = function* () {while (true) {try {yield;} catch (e) {if (e != 'a') throw e;console.log('内部捕获', e);}}
};var i = g();
i.next();try {throw new Error('a');throw new Error('b');
} catch (e) {console.log('外部捕获', e);
}
// 外部捕获 [Error: a]

throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

var gen = function* gen(){try {yield console.log('a');} catch (e) {// ...
  }yield console.log('b');yield console.log('c');
}var g = gen();
g.next() // a
g.throw() // b
g.next() // c

上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。

这种函数体内捕获错误的机制,大大方便了对错误的处理。多个yield表达式,可以只用一个try...catch代码块来捕获错误。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数内部写一个错误处理语句,现在只在 Generator 函数内部写一次catch语句就可以了。

5.Generator.prototype.return():

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。

6.yield*表达式:

yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。

function* concat(iter1, iter2) {yield* iter1;yield* iter2;
}// 等同于

function* concat(iter1, iter2) {for (var value of iter1) {yield value;}for (var value of iter2) {yield value;}
}

上面代码说明,yield*后面的 Generator 函数(没有return语句时),不过是for...of的一种简写形式,完全可以用后者替代前者。反之,在有return语句时,则需要用var value = yield* iterator的形式获取return语句的值。

任何数据结构只要有 Iterator 接口,就可以被yield*遍历。

yield*命令可以很方便地取出嵌套数组的所有成员。

function* iterTree(tree) {if (Array.isArray(tree)) {for(let i=0; i < tree.length; i++) {yield* iterTree(tree[i]);}} else {yield tree;}
}const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];for(let x of iterTree(tree)) {console.log(x);
}
// a
// b
// c
// d
// e

7.作为对象属性的Generator函数:

let obj = {* myGeneratorMethod() {···}
};
//等同于
let obj = {myGeneratorMethod: function* () {// ···
  }
};

8. Generator函数的this

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

function* g() {}g.prototype.hello = function () {return 'hi!';
};let obj = g();obj instanceof g // true
obj.hello() // 'hi!'

如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。也不能跟new命令一起用,会报错。于是可以

将Generator 函数改造成构造函数:

function* gen() {this.a = 1;yield this.b = 2;yield this.c = 3;
}function F() {return gen.call(gen.prototype);
}var f = new F();f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

9.含义:

a.Generator 是实现状态机的最佳结构

var ticking = true;
var clock = function() {if (ticking)console.log('Tick!');elseconsole.log('Tock!');ticking = !ticking;
}//es5var clock = function* () {while (true) {console.log('Tick!');yield;console.log('Tock!');yield;}
};//es6

上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。

b.Generator与协程:

多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。

从实现上看,在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。

由于 JavaScript 是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。

Generator 与上下文

JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。

这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。

Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。

function *gen() {yield 1;return 2;
}let g = gen();console.log(g.next().value,g.next().value,
);

上面代码中,第一次执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码。等遇到yield 1时,gen上下文退出堆栈,内部状态冻结。第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。

10.应用:

a异步操作的同步化表达:

function* loadUI() {showLoadingScreen();yield loadUIDataAsynchronously();hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()// 卸载UI
loader.next()

上面代码中,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面(showLoadingScreen),并且异步加载数据(loadUIDataAsynchronously)。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰。

b.控制流管理:

function* longRunningTask(value1) {try {var value2 = yield step1(value1);var value3 = yield step2(value2);var value4 = yield step3(value3);var value5 = yield step4(value4);// Do something with value4} catch (e) {// Handle any error from step1 through step4
  }
}

然后,使用一个函数,按次序自动执行所有步骤。

scheduler(longRunningTask(initialValue));function scheduler(task) {var taskObj = task.next(task.value);// 如果Generator函数未结束,就继续调用if (!taskObj.done) {task.value = taskObj.valuescheduler(task);}
}

注意,上面这种做法,只适合同步操作,即所有的task都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。

利用for...of循环会自动依次执行yield命令的特性,提供一种更一般的控制流管理的方法。

let steps = [step1Func, step2Func, step3Func];function *iterateSteps(steps){for (var i=0; i< steps.length; i++){var step = steps[i];yield step();}
}

将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。

let jobs = [job1, job2, job3];function* iterateJobs(jobs){for (var i=0; i< jobs.length; i++){var job = jobs[i];yield* iterateSteps(job.steps);}
}

上面代码中,数组jobs封装了一个项目的多个任务,Generator 函数iterateJobs则是依次为这些任务加上yield*命令。

最后,就可以用for...of循环一次性依次执行所有任务的所有步骤。

for (var step of iterateJobs(jobs)){console.log(step.id);
}

c.部署Iterator接口:

利用 Generator 函数,可以在任意对象上部署 Iterator 接口。

d.作为数据结构:

它可以对任意表达式,提供类似数组的接口。(可以用for...of遍历)

11.Generator函数的异步应用:

Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

Thunk函数:

编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。

function f(m) {return m * 2;
}f(x + 5);// 等同于var thunk = function () {return x + 5;
};function f(thunk) {return thunk() * 2;
}

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。

Thunkify模块:生产环境的转换器,建议使用 Thunkify 模块。

Thunk 函数现在可以用于 Generator 函数的自动流程管理。(可以自动执行 Generator 函数)

co模块:

co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co。

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。

这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。

// 数组的写法
co(function* () {var res = yield [Promise.resolve(1),Promise.resolve(2)];console.log(res);
}).catch(onerror);// 对象的写法
co(function* () {var res = yield {1: Promise.resolve(1),2: Promise.resolve(2),};console.log(res);
}).catch(onerror);

co(function* () {var values = [n1, n2, n3];yield values.map(somethingAsync);
});function* somethingAsync(x) {// do something asyncreturn y
}

上面的代码允许并发三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。

12.Async函数

//一个Generator函数 依次读取两个文件
const fs = require('fs');const readFile = function (fileName) {return new Promise(function (resolve, reject) {fs.readFile(fileName, function(error, data) {if (error) return reject(error);resolve(data);});});
};const gen = function* () {const f1 = yield readFile('/etc/fstab');const f2 = yield readFile('/etc/shells');console.log(f1.toString());console.log(f2.toString());
};

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

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

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

(1)内置执行器。

async函数的执行,与普通函数一模一样,只要一行。(例如:asyncReadFile();

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

(2)更好的语义。

(3)更广的适用性。

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

(4)返回值是 Promise。

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

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

基本用法:

async function getStockPriceByName(name) {const symbol = await getStockSymbol(name);const stockPrice = await getStockPrice(symbol);return stockPrice;
}getStockPriceByName('goog').then(function (result) {console.log(result);
});

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

//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 () => {};

async函数返回一个 Promise 对象。

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

async function f() {return 'hello world';
}f().then(v => console.log(v))
// "hello world"

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

async function f() {throw new Error('出错了');
}f().then(v => console.log(v),e => console.log(e)
)
// Error: 出错了

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有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"

上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log

await命令:

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

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

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

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个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后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。防止出错的方法,也是将其放在try...catch代码块之中。

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

下面的例子使用try...catch结构,实现多次重复尝试。

const superagent = require('superagent');
const NUM_RETRIES = 3;async function test() {let i;for (i = 0; i < NUM_RETRIES; ++i) {try {await superagent.get('http://google.com/this-throws-an-error');break;} catch(err) {}}console.log(i); // 3
}test();

使用注意点:

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

2.将继发写成同时触发:

//继发
let foo = await getFoo();
let bar = await getBar();/同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

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

如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

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);
}

一个栗子:

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

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

另一个栗子:

async function logInOrder(urls) {// 并发读取远程URLconst 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,因此实现了按顺序输出。

异步遍历器:

对象的异步遍历器接口,部署在Symbol.asyncIterator属性上面。不管是什么样的对象,只要它的Symbol.asyncIterator属性有值,就表示应该对它进行异步遍历。

for...of循环用于遍历同步的 Iterator 接口。新引入的for await...of循环,则是用于遍历异步的 Iterator 接口。

异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。

13.Class

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point {constructor() {// ...
  }toString() {// ...
  }toValue() {// ...
  }
}// 等同于

Point.prototype = {constructor() {},toString() {},toValue() {},
};

类的内部所有定义的方法,都是不可枚举的(non-enumerable)。这一点与 ES5 的行为不一致。

Object.assign方法可以很方便地一次向类添加多个方法。

class Point {constructor(){// ...
  }
}Object.assign(Point.prototype, {toString(){},toValue(){}
});

与函数一样,类也可以使用表达式的形式定义。

const MyClass = class Me {getClassName() {return Me.name;}
};
let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not defined
 

上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是MeMe只在 Class 的内部代码可用,指代当前类。

如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。

采用 Class 表达式,可以写出立即执行的 Class。

let person = new class {constructor(name) {this.name = name;}sayName() {console.log(this.name);}
}('张三');person.sayName(); // "张三"

类不存在变量提升(hoist),这一点与 ES5 完全不同。

私有方法:一种做法是在命名上加以区别。(_methods)

另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

class Widget {foo (baz) {bar.call(this, baz);}// baz变成当前函数的私有方法
}function bar(baz) {return this.snaf = baz;
}

私有属性:(#x)

this的指向问题:

class Logger {constructor() {this.printName = this.printName.bind(this);}// ...
}

class Logger {constructor() {this.printName = (name = 'there') => {this.print(`Hello ${name}`);};}// ...
}

class的静态方法:

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo {static classMethod() {return 'hello';}
}Foo.classMethod() // 'hello'var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

父类的静态方法,可以被子类继承。静态方法也是可以从super对象上调用的。

class的静态属性和实例属性:

// 老写法class Foo {
}Foo.prop = 1;
Foo.prop // 1
// 新写法
class Foo {static prop = 1; }
 

ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined。

class的继承:

class ColorPoint extends Point {constructor(x, y, color) {super(x, y); // 调用父类的constructor(x, y)this.color = color;}toString() {return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

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

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

Object.getPrototypeOf方法可以用来从子类上获取父类。Object.getPrototypeOf(ColorPoint) === Point,可以使用这个方法判断,一个类是否继承了另一个类。

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。

14.Moduel

ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。(运行时加载)

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

转载于:https://www.cnblogs.com/jiuyejiu/p/7986938.html

es6的初级简易总结相关推荐

  1. webstorm 创建react组件_webstorm的下载以及React环境搭建

    前言:本文我们将讨论ubuntu中webstorm的安装以及基本React环境搭建 一.安装JDK 下载webstorm之前我们要确定已经下载了jdk,这是必须的否则会在之后的操作提示你无法打开wen ...

  2. React项目的打包与部署到腾讯云

    腾讯云送了30天的免费试用,于是有了把react项目部署到上面的想法.项目是默认生成的,只是一个页面,但是这个过程中也遇到了不少麻烦与问题.下面来具体梳理下: create-react-app 来自F ...

  3. 五子棋java判断平局_2020-10-03 Java初级项目——从零开始制作一个简易五子棋游戏...

    一.棋盘的绘制 使用JFrame容器制作五子棋的窗体 创建一个类--UI,如下: public class UI { private JFrame frame = new JFrame(); publ ...

  4. java五子棋_Java初级项目——从零开始制作一个简易五子棋游戏

    一.棋盘的绘制 使用JFrame容器制作五子棋的窗体 创建一个类--UI,如下: public 使用JPanel和Graphics画出大小为15X15的棋盘 再创建一个新的类--Chessboard, ...

  5. 【C语言初阶】——简易版·扫雷(9*9)【运行逻辑思维导图+细节讲解+源码】【初级】

    目录 一.扫雷游戏的运行逻辑 二.代码逻辑讲解+源码 1.打印一个简易的游戏开始菜单 2.创建数组储存数据并初始化数组 代码逻辑讲解 源码 3.布置雷 代码逻辑讲解 源码 4.排雷 代码逻辑 源码 三 ...

  6. 扫雷html5简单初级,纯原生JS用面向对象class方法实现简易扫雷小游戏

    Demo介绍 纯原生js 实现 且用ES6语法class写的扫雷小游戏:布局为10*10的网格,随机生成10-20个雷. 左键点击扫雷.右键标记该地方有雷.该demo下载下来复制到html文件中直接可 ...

  7. 初级前端自学react-native,必备知识点(ES6+ReactJS+flexbox)

    我们在学会搭建react-native环境之后,打开项目根目录,看到很多个文件,但是最起眼的应该就是那俩js兄弟文件了 我们一看那名字就知道,我们接下来的任务就是要弄它们: 我们用编辑器打开项目根目录 ...

  8. 简易购物车(最全代码解析)JavaScript(ES6面向对象)

    <!DOCTYPE html> <html><head><meta charset="utf-8" /><title>E ...

  9. es6简易教程(for react)

    ES6 本内容修改自:https://segmentfault.com/a/1190000015288439 基于nodejs的js独立运行环境 http://www.runoob.com/nodej ...

最新文章

  1. Nilearn教程系列(4)-脑部地图集绘制
  2. ClickHouse最详细的入门教程(一):部署运行
  3. C++多态案例三-电脑组装
  4. 用java编写一个简单计算器
  5. 数据库简单实用(一)
  6. ZT:公司绝对不会告诉你的潜规则(何杨)
  7. 从技术角度比较CCD与CMOS的区别
  8. 【ICPC 2021网络赛2】The 2021 ICPC Asia Regionals Online Contest (II)签到题5题
  9. checking build system type... ./config.guess: unable to guess system type/you must specify one
  10. 最新服务器处理器天梯,服务器CPU性能排行榜天梯图(双路/三路/四路)
  11. 苏宁易购:苏宁小店将获4.5亿美元增资
  12. 苹果开发者App Store绑定连连跨境支付收款教程!
  13. 新的时间处理工具joda
  14. 期望方差和贝叶斯概率
  15. ubuntu 20.04 设置DNS
  16. Selenium的文件上传和操作Cookie等方法_Sinno_Song_新浪博客
  17. Vivado综合设置之-gated_clock_conversion
  18. chatgpt api请求样例
  19. 如何卸载windows服务
  20. 每日一题【35】解析几何-抛物线的焦点弦长公式

热门文章

  1. android自定义横向时间轴,Android自定义时间轴的实现过程
  2. 组件服务-计算机-我的电脑出现红色向下箭头的解决办法
  3. python编程思维代码_Python编程快速上手——强口令检测算法案例分析
  4. 现在电脑的主流配置_2019~2020电脑配置清单主流配件(下)
  5. R语言线性回归预测网页流量
  6. python pandas series加速原理_python pandas中对Series数据进行轴向连接的实例
  7. c++冒泡排序_python+C、C++混合编程的应用
  8. Scrapy 框架爬取 武动乾坤小说
  9. finalshell文件列表不显示_软网推荐:文件变动我知晓
  10. Docker 中的网络管理与集群构建