前端工作室专注前端开发
 博客首页

《ES6标准入门》(第3版作者:阮一峰)读书笔记 9-18章

2021-03-16 08:00:00
JS基础与深入
46
文章图片

国内十分有名的一本书--《ES6标准入门》进行个人的读书总结,对书中的知识点进行实践验证和记录

《ES6标准入门》(第3版作者:阮一峰)读书笔记 9-18章

第10章 Symbol

10.1 概述

ES6引入一个新的原始数据类型Symbol,前六种分别是:Undefined、Null、、Boolean、String、Number、Object。可以作为属性名。保证每个属性的名字都是独一无。

let s = Symbol();
typeof s; // "symbol"

Symbol函数可以接受一个字符串作为参数,主要是为了区分。

var s1 = Symbol('foo');
var s2 = Symbol('bar');
console.log(s1,s2) // Symbol(foo) Symbol(bar)

如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后再生成一个Symbol值。

const obj = {
    toString(){
        return 'abc'
    }
}
const sym = Symbol(obj);
sym; // Symbol(abc)
const obj = {}
const sym = Symbol(obj);
sym; // Symbol([object Object])

没有参数的情况

var s1 = Symbol();
var s2 = Symbol();
s1 === s2; // false

有参数的情况

var s1 = Symbol('foo');
var s2 = Symbol('foo');
s1 === s2; // false

不能与其他值进行运算,否则会报错。

var sym = Symbol('foo');
"your symbol is" + sym // Uncaught TypeError: Cannot convert a Symbol value to a string
var sym = Symbol('foo');
`your symbol is ${sym}`  // Uncaught TypeError: Cannot convert a Symbol value to a string

但是Symbol值可以显式转换为字符串。

var sym = Symbol('symbol value')
String(sym) // "Symbol(symbol value)"
sym.toString() // "Symbol(symbol value)"

另外Symbol值也可以转为布尔值,但是不能转换为数值。

var sym = Symbol('symbol value')
Boolean(sym) // true
var sym = Symbol('symbol value')
Number(sym) // Uncaught TypeError: Cannot convert a Symbol value to a number

10.2 作为属性名的Symbol

Symbol值是不相等,可以作为标识符用于对象的属性名。 var mySymbol = symbol(); 第一种写法 var a = {}; a[mySymbol] = "hello!" 第二种写法 var a = { [mySymbol]: 'hello!' } 第三种写法 var a = {}; Object.defineProperty(a,mySymbol,{value:'Hello!'}) 不能使用点运算符设置Symbol属性名

var mySymbol = Symbol();
var a = {};
a.mySymbol = 'Hello!';
console.log(a[mySymbol]) // undefined
console.log(a['mySymbol']) // Hello!'(错误的设置了一个字符串属性名)

在对象内部定义Symbol属性时,必须放在方括号内,否则又会错误的设置为字符串属性名。

let s = Symbol();
let obj ={
    [s]: function(){}
}
obj[s]; // ƒ (){}

采用对象增强的写法

let s = Symbol();
let obj ={
    [s](){}
}

Symbol类型还可以定义一组常量,保证这组常量的值不相等。

let obj= {
    a: Symbol('a'),
    b: Symbol('b')
}

属性a和属性b绝对不相等了。

const a = Symbol();
const b = Symbol();
function judge(type){
    switch(type){
        case a: return 'a';
        case b: return 'b';
        default: return 'default';
    }
}
judge(a); // "a"

常量使用Symbol值最大好处就是,不可能有其他相同的值。

10.3 实例:消除魔术字符串

10.4 属性名的遍历

Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。Object.getOwnPropertySymbols方法返回一个数组。

let obj = {
    a: 'a',
    b: Symbol('b'),
    [Symbol('c')]: 'c',
    [Symbol('d')]: 'd'
}
for(let key in obj){
    console.log(key)
} //  a b
console.log(Object.keys(obj)) // ["a", "b"]
console.log(Object.getOwnPropertyNames(obj)) // ["a", "b"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(c), Symbol(d)]

全新的API--Reflect.ownKeys方法可以返回所有类型的键名。

let obj = {
    a: 'a',
    b: Symbol('b'),
    [Symbol('c')]: 'c',
    [Symbol('d')]: 'd'
}
Reflect.ownKeys(obj) // ["a", "b", Symbol(c), Symbol(d)]

10.5 Symbol.for()、Symbol.keyFor()

有时候我们使用同一个Symbol值,可以使用Symbol.fo。

var s1 = Symbol.for('a'); // 有时直接返回,无时创建
var s2 = Symbol.for('a');
s1 === s2 // true

但是

var s1 = Symbol('b');
var s2 = Symbol.for('b');
s1 === s2 // false
var s1 = Symbol('b');
var s2 = Symbol('b'); // 每次都会创建新的,而且不相等
s1 === s2 // false

Symbol.keyFor方法返回一个已登记的Symbol类型值的key

var s1 = Symbol.for('foo');
Symbol.keyFor(s1); // "foo"
var s2 = Symbol('foo');
Symbol.keyFor(s2); // undefined

10.6 实例:模块的Singleton模式

10.7 内置的Symbol值

10.7.1 Symbol.hasInstance

对象的Symbol.hasInstance属性指向内部的一个方法,对象使用instanceof运算符会调用这个方法,判断该对象是否未某个构造函数的实例。比如,foo instanceof Foo在语言内部实际调用的是Foo[Symbol.hasInstance](foo)

class MyClass{
    [Symbol.hasInstance](foo){
        return foo instanceof Array;
    }
}
[1,2,3] instanceof new MyClass(); // true(实际调用的是内部的[Symbol.hasInstance](foo)方法)

最好不要这样子弄,改变了正确返回,例如上例应该返回false

10.7.2 Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时是否可以展开。

let arr = [1,2];
['3','4'].concat(arr,'5') // ["3", "4", 1, 2, "5"](默认是可以展开的)
arr[Symbol.isConcatSpreadable] // undefined
let arr2 = [1,2];
arr2[Symbol.isConcatSpreadable] = false;
['3','4'].concat(arr2,'5') // ["3", "4",[1,2] , "5"](不能展开了,直接整个推进去了)

10.7.3 Symbol.species

对象的Symbol.species属性指向当前对象的构造函数。创造实例时默认会调用这个方法,使用这个属性返回的函数当作构造函数来创造新的实例对象。

class MyArray extends Array{
    static get [Symbol.species](){
        return Array;
    }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x**2)
console.log(mapped instanceof MyArray) // false
console.log(mapped instanceof Array) // true(使用这个属性导致构造函数被替换,那为什么要使用它呢)

10.7.4 Symbol.match

对象的Symbol.match属性指向一个函数,执行str.match(obj)时,如果该属性存在,会调用它返回该方法的返回值。 String.prototype.match(regexp)等同于regexp[Symbol.match](this)

class MyMatcher {
    [Symbol.match](str){
        return 'leezw'.indexOf (str)
    }
}
'e'.match(new MyMatcher()) // 1

10.7.5 Symbol.replace

对象的Symbol.replace属性指向一个方法,当对象被String.prototype.replace方法调用时会放回该方法的返回值。 String.prototype.replace(seachValue, replaceValue) //等同于 searchValue[Symbol.replace](this,replaceValue)

const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x,'world') // ["Hello", "world"](两个参数被打印出来)

10.7.6 Symbol.search

对象的Symbol.search属性指向一个方法,当对象被String.prototype.search方法调用时会返回该方法的返回值。 String.prototype.search(regexp) // 等同于 regexp[Symbol.search](this)

class MySearch{
    construtor(value){
        this.value = value;
    }
    [Symbol.search](str){
        return str.indexOf(this.value)
    }
}
'leezhenwang'.search(new MySearch('foo')) // -1

10.7.7 Symbol.split

对象的Symbol.split属性指向一个方法,当对象被String.prototype.split方法调用时会返回该方法的返回值。 String.prototype.split(separator,limit) // 等同于 separator[Symbol.split](this,limit)

class MySplit{
    constructor(value){
        this.value = value;
    }
    [Symbol.split](str){
        return str.indexOf(this.value)
    }
}
'lzw'.split(new MySplit('l')) // 0

10.7.8 Symbol.iterator

对象的Symbol.iterator属性指向该对象的默认遍历器。 使对象可以使用for of

class Iterator {
    *[Symbol.iterator](){
        let keys = Object.keys(this);
        let i = 0
        while(this[keys[i]]){
            yield this[keys[i++]]
        }
    }
}
let newIterator = new Iterator();
newIterator.a = 'a';
newIterator.b = 'b';
for(let key of newIterator){
    console.log(key)
}

10.7.9 Symbol.tpPrimitive

对象的Symbol.toPromitive属性指向一个方法,对象转换为原始类型的值使会调用这个方法,返回该对象对应的原始类型值。

let obj ={
    [Symbol.toPrimitive](hint){
        switch(hint){
            case 'number': return 123;
            case 'string': return 'lzw';
            case 'default': return 'default';
            default: throw new Error();
        }
    }
}
console.log(2*obj); // 246
console.log(3+obj); // 3default
console.log(obj == 'default') // true
String(obj) // "lzw"

10.7.10 Symbol.toStringTag

对象的Symbol.toStringTag属性指向一个方法,在对象调用Object.prototype.toString方法时,如果这个属性存在,其返回值会出现在toString方法返回的字符串中,表示对象的类型。

console.log({[Symbol.toStringTag]: 'foo'}.toString()) // [object foo]

10.7.11 Symbol.unscopables

对象的Symbol.unscopeables属性指向一个对象,指定了使用with关键字时那些属性会被with环境排除。

Array.prototype[Symbol.unscopables] // {copyWithin: true, entries: true, fill: true, find: true, findIndex: true}

第11章 Set和Map数据结构

11.1 Set

11.1.1 基本用法

ES6提供新的数据结构Set。类似于数组但是成员值是唯一的,没有重复。Set本身是一个构造函数,用来生成Set数据结构。

const s = new Set();
[1,1,2,2,3,3,4,4,5,5,6,6].forEach(x=>s.add(x))
console.log(s) // Set(6) {1, 2, 3, 4, 5, 6}(不会重复添加值)

Set函数接收一个数组(或者其他具有iterable接口的其他数据结构)作为参数,用来初始化。

const set = new Set([1,1,1,2,3,4,6,4,4,5])
console.log(set) // Set(6) {1, 2, 3, 4, 6, 5}(不会重复)
const set = [...document.querySelectorAll('div')]; // 具有iterable接口
console.log(set)

//用于去除重复成员 [...new Set(array)] 注意'5'和5不会被认为是同样的值,但是NaN会被认为等于自身。

const set = new Set();
set.add(5);
set.add('5');
set.add(NaN);
set.add(NaN);
console.log(set) // Set(3) {5, "5", NaN}

两个对象永远是不相等的

const set = new Set();
set.add({});
set.add({});
set.add({a:1});
set.add({a:1});
console.log(set) // Set(4) {{…}, {…}, {…}, {…}}

11.1.2 Set实例的属性和方法

Set结构实例有以下属性 Set.prototype.constructor: 构造函数,默认是Set函数。 Set.prototype.size: 返回Set实例的成员总数。 Set实例的操作方法: add(value): 添加某个值,返回Set结构本身 delete(value): 删除某个值,返回一个布尔值,表示删除成功与否。 has(value): 是否包含某个值。 clear(): 清除所有成员 const set = new Set();

set.add(1);
set.add(2);
set.add(2);
console.log(set.size); // 2(2重复只写入一个)
console.log(set.has(1)); // true
console.log(set.has(2)); // ture
console.log(set.has(3)); // false
console.log(set.delete(2)) // true
console.log(set) // Set(1) {1}
set.delete(3) // false(没有这个值可供删除)

去除数组重复的方法

[...(new Set([1,2,2,3,4]))] // [1, 2, 3, 4]

或者

Array.from(new Set([1,2,2,3,4,4])) // [1, 2, 3, 4]

11.1.3 遍历操作

Set结构的实例有4个遍历方法。 keys(): 返回键名的遍历器 values(): 返回键值的遍历器 entries(): 返回键值对的遍历器 forEach(): 使用回调函数遍历每个成员

let set = new Set([1,2,3])
for(let item of set.keys()){
    console.log(item)
} // 1 2 3
for(let item of set.values()){
    console.log(item)
} // 1 2 3
for(let item of set.entries()){
    console.log(item)
} // [1, 1] [2, 2] [3, 3](两个成员值一样的)

Set结构实例默认可遍历,其默认遍历器生成函数就是它的values方法

Set.prototype[Symbol.iterator] === Set.prototype.values
let set = new Set([1,2,3]);
set.values(); // SetIterator {1, 2, 3}

可以省略values方法,直接for of 循环遍历Set

let set = new Set([1,2,3]);
for(let item of set){
    console.log(item) 
} // 1 2 3

forEach方法对每个成员执行某种操作。

let set = new Set([1,2,3]);
set.forEach((value,key)=>console.log(value*2,key)) 
set; // Set(3) {1, 2, 3}

遍历的应用 拓展运算符内部使用for of循环,所以也可以用于Set结构。

[...(new Set([1,2,2,3,3,4]))] // [1, 2, 3, 4](去除重复的数组成员)

数组的map和filter方法也可以用于Set

let set = new Set([1,2,3]);
set = new Set([...set].map(x=>x**2))
console.log(set) // Set(3) {1, 4, 9}
let set = new Set([1,2,3]);
set = new Set([...set].filter(x=>x>1))
console.log(set) // Set(2) {2, 3}

Array.from方法 遍历过程改变原来的Set结构的方法 方法一

let set = new Set([1,2,3]);
set = new Set([...set].map(x=>x**2))
console.log(set) // Set(3) {1, 4, 9}

方法二

let set = new Set([1,2,3]);
set = new Set(Array.from(set,val => val**2))
console.log(set) // Set(3) {1, 4, 9}

11.2 weakSet

11.2.1 含义

weakSet结构与set类似,也是不重复的值的集合。 区别一: 成员只能是对象

const ws = new WeakSet();
ws.add(1); // 报错 Uncaught TypeError: Invalid value used in weak set
const ws = new WeakSet();
ws.add({}); // 对象没问题

区别二: WeakSet的对象是弱引用,垃圾回收机制不会管weakSet有没有对该对象进行引用,所以可能会莫名其妙的消失了。该结构不可遍历。 所以为什么还要使用它?

11.2.2 语法

WeakSet是一个构造函数,可以使用new 命令创建WeakSet数据结构。 const ws = new WeakSet() 任何具有iterator接口的对象都可以作为WeakSet的参数。

const ws = new WeakSet([[1,2],[3,4]]); // 数组的每一项必须是对象,数组也是对象
ws.add({});
ws; // WeakSet {Array(2), {…}, Array(2)}

拥有add、delete、has三个方法,没有size属性,不能遍历,因为成员随时消失

ws.size // undefined

使用例子

const foos = new WeakSet();
class Foo{
    constructor(){
        foos.add(this)
    }
    method(){
        if(!foos.has(this)){
            throw new TypeError('只能在实例上调用')
        }else{
            console.log('在实例上调用了')
        }
    }
}
let newFoo = new Foo(); // 实例在时WeakSet对应成员就肯定存在,实例不再时也不用担心内存泄露
newFoo.method(); // 在实例上调用了
Foo.prototype.method(); // 报错 Uncaught TypeError: 只能在实例上调用

11.3 Map

11.3.1 含义的基本用法

js对象本质上是键值对的集合,但是只能使用字符串作为键,所以有了新的数据结构Map。反正有对应关系就行了。

const map = new Map();
const obj = {a: 1};
map.set(obj,'content');
console.log(map.get(obj)) // content
console.log(map.has(obj)) // true
console.log(map.delete(obj)) // true
console.log(map.has(obj)) // false

可以接受一个数组作为参数。数组每一项包含键值对。

const map = new Map([
    ['name','lzw'],
    ['age',18],
    ['job','worker']
])
console.log(map.size) // 3
console.log(map.has('name')) // true
console.log(map.get('name')) // lzw
console.log(map.delete('name')) // ture
console.log(map) // Map(2) {"age" => 18, "job" => "worker"}

不仅仅是数组,任何具有Iterator接口且每个成员都是双元素数组的数据结构都可以作为Map构造函数参数。所以Set和Map都可以生成新的Map。

let arr = [
    ['a','1'],
    ['b','2']
]
const set = new Set(arr);
const map1 = new Map(set); // 使用Set数据类型

const map2 = new Map(arr)
const map3 = new Map(map2) // 使用Map数据类型
console.log(map1,map3) // Map(2) {"a" => "1", "b" => "2"} Map(2) {"a" => "1", "b" => "2"}

同一键值后面的值会覆盖前面的值

map1.set(1,2).set(1,3) // Map(3) {"a" => "1", "b" => "2", 1 => 3}(覆盖了前面的值)

未知值返回undefined

map1.get(44) // undefined

注意:只有对同一个对象的引用,Map结构才将其视为同一个键。

const map = new Map();
map.set(['a'],111);
map.get(['a']); // undefined(两个数组虽然相等,当时内存地址不一样)

应改为

let arr = ['a']
const map = new Map();
map.set(arr,111);
map.get(arr);

引用类型值,内存地址不一致的,引用指向不一致的,即使数值相同也当作不同的键值。而简单类型值仅仅值相等即可。0 和-0视为相等,NaN视为与自身相等。null === null 、undefined === undefined

let arr = ['a']
const map = new Map();
map.set(1,111);
map.get(1); // 111(可以取出来值)
map.set(-0,000)
map.get(0) // 0
map.set(NaN,NaN)
map.get(NaN) // NaN

11.3.2 实例属性和操作方法

Map结构的实例的属性和操作方法 size属性

let map = new Map([
    [1,1],
    [2,2]
])
map.size; // 2

set(key,value)无则创建有则覆盖

let map = new Map([
    [1,0],
])
map.set(1,1).set(2,2).set(3,3);
console.log(map) // Map(3) {1 => 1, 2 => 2, 3 => 3}

get(key)

let map = new Map([
    [1,0],
])
map.get(1)) // 0

has(key)

let map = new Map([
    [1,0],
])
map.has(1) // true

delete(key)

let map = new Map([
    [1,0],
])
map.delete(1);
console.log(map) // Map(0) {}

clear()

let map = new Map([
    [1,0],
])
map.clear();
console.log(map) // Map(0) {}

11.3.3 遍历方法

Map原生提供了3个遍历器生成函数和1个遍历方法

let map = new Map([
    ['key1','value1'],
    ['key2','value2']
])
for(let key of map.keys()){
    console.log(key)
} // key1 key2
for(let value of map.values()){
    console.log(value)
} // value1 value2
for(let item of map.entries()){
    console.log(item)
} // ["key1", "value1"] ["key2", "value2"]
for(let [key,value] of map.entries()){ //解构上面的item
    console.log(key,value)
} // key1 value1 // key2 value2
for(let [key,value] of map){ // entries()可以省略
    console.log(key,value)
} // key1 value1 // key2 value2

entries()为什么可以省略? 因为

map[Symbol.iterator] === map.entries // true

结合拓展运算符

let map = new Map([
    [1,'one'],
    [2,'two'],
])
console.log([...map.keys()]) // [1, 2]
console.log([...map.values()]) // ["one", "two"]
console.log([...map.entries()]) // [Array(2), Array(2)]
[...map] // [Array(2), Array(2)]

再结合map和filter方法

let map = new Map([
    [1,'one'],
    [2,'two'],
    [3,'three'],
])
const map1 = new Map([...map].filter(([key,value])=> key<3))
const map2 = new Map([...map].map(([key,value])=>[key**2,value]))
console.log(map1,map2) // Map(2) {1 => "one", 2 => "two"} Map(3) {1 => "one", 4 => "two", 9 => "three"}

forEach方法

let map = new Map([
    [1,'one'],
    [2,'two'],
    [4,'three'],
])
map.forEach((value,key)=>{ // 值和键的位置互调了
    console.log(value,key)
})
// one 1
// two 2
// three 4

第二个参数用于绑定this

let map = new Map([
    [1,'one'],
    [2,'two'],
    [4,'three'],
])
let obj = {
    forEachFunc: function(value,key){
        console.log(value,key)
    }
}
map.forEach(function(value,key){ // 这时候不能使用箭头函数了
    this.forEachFunc(value,key)
},obj)

11.3.4 与其他数据结构的互相转换

Map转换为数组

let map = new Map([
    [1,'one'],
    [2,'two'],
    [3,'three'],
])
console.log([...map]) // [Array(2), Array(2), Array(2)]

数组转换为Map

let map = new Map([
    [1,'one'],
    [{a:1},'two'],
])
console.log(map) // Map(2) {1 => "one", {…} => "two"}

Map转为对象 如果Map的所有键都是字符串,则可以转为对象。

let map = new Map([
    ['a','one'],
    ['b','two'],
])
function strMapToObj(strMap){
    let obj = Object.create(null);
    for(let [key,value] of strMap){
        obj[key] = value;
    }
    return obj;
}
strMapToObj(map); // {a: "one", b: "two"}

对象转为Map

let obj1 = {a: 1,b:2};
function objToMap(obj){
    let strMap = new Map();
    for(let [key,value] of Object.entries(obj)){
        strMap.set(key,value)
    }
    return strMap;
}
objToMap(obj1) // Map(2) {"a" => 1, "b" => 2}

Map转为JSON 情况一:Map的键名都是字符串,这时可以选择转为对象JSON

function strMapToJson(strMap){
    return JSON.stringify(strMapToObj(strMap))
}
function strMapToObj(strMap){
    let obj = Object.create(null);
    for(let [key,value] of strMap){
        obj[key] = value;
    }
    return obj;
}
let map = new Map([
    ['a','one'],
    ['b','two'],
])
strMapToJson(map) // "{"a":"one","b":"two"}"

情况二:Map的键名有非字符串,这时可以转为数组JSON

let map = new Map([
    ['a','one'],
    ['b','two'],
    [{c:3},'three'],
])
function mapToArrayJson(map){
    return JSON.stringify([...map])
}
mapToArrayJson(map) // "[["a","one"],["b","two"],[{"c":3},"three"]]"

JSON转为Map 情况一:

function objToMap(obj){
    let strMap = new Map();
    for(let [key,value] of Object.entries(obj)){
        strMap.set(key,value)
    }
    return strMap;
}
function jsonToStrMap(jsonStr){
    return objToMap(JSON.parse(jsonStr))
}
jsonToStrMap('{"a":1,"b":2}') // Map(2) {"a" => 1, "b" => 2}

情况二:整个JSON是一个数组

function jsonToMap(jsonStr){
    return new Map(JSON.parse(jsonStr))
}
console.log(jsonToMap('[["a","one"],["b","two"],[{"c":3},"three"]]')) // Map(3) {"a" => "one", "b" => "two", {…} => "three"}

11.4 WeakMap

WeakMap结构与Map结构类似,用于生成键值对的集合。 区别一: WeakMap只接受对象作为键名(null除外)

const wm = new WeakMap();
wm.set(1,1) // 报错 Uncaught TypeError: Invalid value used as weak map key
const wm = new WeakMap();
wm.set(null,1) // 报错 Uncaught TypeError: Invalid value used as weak map key
const wm = new WeakMap();
wm.set({},1) 
console.log(wm) // WeakMap {{…} => 1}

区别二: WeakMap的键名所指对象不计入垃圾回收机制,防止出现垃圾回收机制不释放内存,需要手动释放的问题。

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
    [e1,'foo元素'],
    [e2,'bar元素']
]
// 不需要e1 e2时
// 必须手动删除
arr[0] = null; // 忘记清除就会内存泄露
arr[1] = null;

利用弱引用的例子

const wm = new WeakMap();
const element = document.getElementById('one-google');
wm.set(element,'googlepage');
wm.get(element) // 'googlepage'

DOM节点对象的引用计数是1,而不是2。取消引用后内存会立即释放,即DOM节点被删除时,键值对会立即释放。WeakMap适用于对象将来消失的场景,防止内存泄露。

const wm = new WeakMap();
let key = {};
let obj = {foo:1};
wm.set(key,obj);
obj = null;
wm.get(key); // {foo: 1}(正常引用,已经存好该对象的内存地址)
key = null;
wm.get(key); // undefined(弱引用,根据key的来判断)

为什么依然可以取到值?因为键值对的值已经存好了指向{foo: 1}的指针。

11.4.2 WeakMap的语法

// size、forEach、clear方法都不存在 const wm = new WeakMap(); console.log(wm.size); // undefined console.log(wm.forEach) // undefined console.log(wm.clear) // undefined 4个方法:get()、set()、has()、delete()

11.4.3 WeakMap示例

11.4.4 WeakMap的用途

用途一: 注册监听事件的listener对象很适用WeakMap来实现。

const listener = new WeakMap();
const element1 = document.getElementById('user-content-wrapper')
function handler1(){
    console.log('点击了')
}
listener.set(element1,handler1); // DOM节点消失监听函数自动消除
element1.addEventListener('click',listener.get(element1),false)

用途二:部署私有属性 略

第12章 Proxy

12.1 概述

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改。设置了一个“拦截”层,外界访问必须先通过这层拦截。

var obj = new Proxy({},{
    get: function(target,key,receiver){
        console.log(`${target},${key}`)
        return Reflect.get(target,key,receiver);
    },
    set: function(target,key,value,receiver){
        console.log(`${target},${key},${value}`)
        return Reflect.set(target,key,value,receiver);
    }
})
obj.count = 1;
// [object Object],count,1
++obj.count;
// [object Object],count
// [object Object],count,2

Proxy实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。 ES6原生提供Proxy构造函数,用于生成Proxy实例。 var proxy = new Proxy(target,handler)

var proxy = new Proxy({},{
    get: function(target,property){
        return 'lzw'
    }
})
console.log(proxy.a) // lzw
console.log(proxy.b) // lzw

技巧:将Proxy对象设置到object.proxy属性,从而可以在object对象上调用。 var object = { proxy: new Proxy(target,value) } Proxy实例也可以作为其他对象的原型

var proxy = new Proxy({},{
    get: function(){
        return 'lzw'
    }
})
let obj = Object.create(proxy);
obj.name; // lzw(原型上读取该属性时被拦截)

同一个拦截器函数可以设置多个操作 get(target,propKey,receiver) 拦截对象属性的读取,最后一个参数receiver是一个可选对象。 set(target,propKey,value,receiver) 拦截对象属性的设置,返回一个布尔值 has(target,propKey) 拦截propKey in proxy 操作,返回一个布尔值 deleteProperty(target,propKey) 拦截delete proxy[propKey]操作,返回一个布尔值 ownKyes(target) 拦截Object.geoOwnPrppertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。盖方法返回目标对象所有自身属性属性名。 getOwnPropertyDescriptor(target,propKey) 拦截Object.getOwnPropertyDescriptor(proxy,propKey) defineProperty(target,propKey,propDesc) Object.defineProperty(proxy,propKey,propDesc) 拦截Object.defineProperty(proxy,propKey,propDesc)、Object.defineProperties(proxy,propDescs),返回一个布尔值 preventExtentsions(target) 拦截Object.preventEventsions(proxy),返回一个布尔值 getPrototypeOf(target) 拦截Object.getPrototypeOf(proxy),返回一个布尔值 isExtensible(target) 拦截Object.isExtensible(proxy),返回一个布尔值 setPrototypeOf(target,proto) 拦截Object.setPrototypeOf(proxy,proto),返回一个布尔值。 如果目标对象时函数,那么还有两种额外操作可以拦截 apply(target,object,args) 拦截Proxy实例,并将其作为函数调用的操作,比如proxy(...args)、proxy.call(object,...args)、proxy.apply(...) construct(target,args) 拦截Proxy实例作为构造函数调用操作,比如 new Proxy(...args)

12.2 Proxy实例的方法

12.2.1 get()

get方法用于拦截某个属性的读取操作。

var person = {
    name: 'lzw'
}
var proxy = new Proxy(person,{
    get: function(target,property){
        if(property in target){
            return target[property];
        }else{
            throw new ReferenceError(`${property} property does not exist.`)
        }
    }
})
proxy.a; // Uncaught ReferenceError: a property does not exist.

get方法可以继承

// 接上例
let obj1 = Object.create(proxy);
obj1.xxx; // Uncaught ReferenceError: xxx property does not exist.

如果一个属性不可配置或者不可写,则该属性不能被代理,通过Proxy对象访问该属性将会报错。

var target = Object.defineProperties({},{
    foo:{
        value: 'foo',
        configurable: false,
    },
    bar: {
        value: 'bar',
        writable: false
    }
})
const handler = {
    get(target,propKey){
        return 'foobar'
    }
}
const proxy = new Proxy(target, handler);
proxy.bar; // Uncaught TypeError: 'get' on proxy: property 'bar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'bar' but got 'foobar')
proxy.foo; // Uncaught TypeError: 'get' on proxy: property 'bar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'bar' but got 'foobar')

12.2.2 set()

set方法拦截某个属性的赋值操作

let validator ={
    set: function(target,property,value){
        if(property === 'num'){
            if(value < 0){
                throw new Error('不要写入负数')
            }
        }
        target[property] = value;// 其他属性或者符合要求的数字直接保存
    }
};
let numObj = new Proxy({},validator);
numObj.a = 1; // 正常写入不报错
numObj.num = 1// 正常写入不报错
numObj.num = -1; // 报错 Uncaught Error: 不要写入负数

12.2.3 apply()

apply方法拦截函数的调用、call和apply操作 apply方法可以接受3个参数,分别是目标对象、目标对象的上下文对象和目标对象的参数数组

var handler = {
    apply(target,ctx,args){
        return Reflect.apply(...arguments)*2
    }
}
function add(a,b){
    return a+b;
}
var proxy = new Proxy(add, handler);
proxy(1,2) // 6(可以改变返回结果)
proxy.call(null,1,2) // 6
proxy.apply(null,[1,2]) // 6

12.2.4 has()

has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时会被拦截。

var handler = {
    has(target,key){
        if(key[0] === '_'){
            return false
        }
        return key in target;
    }
}
var target = {_prop:'foo',prop: 'foo'}
var proxy = new Proxy(target,handler)
'_prop' in proxy; // false
for(let key in proxy){
    console.log(key)
} // _prop prop(不能拦截for...in)

不能拦截for in操作 不可配置和禁止拓展,该操作会报错

var handler = {
    has(target,key){
        if(key[0] === '_'){
            return false
        }
        return key in target;
    }
}
var target = {_prop:'foo',prop: 'foo'}
var proxy = new Proxy(Object.freeze(target),handler) // 冻结对象
'_prop' in proxy; // 报错 Uncaught TypeError: 'has' on proxy: trap returned falsish for property '_prop' which exists in the proxy target as non-configurable

12.2.5 construct()

construct方法用于new命令

var func = function(){}
var handler = {
    construct: function(target,args){
        return {b: args[0]*10}
    }
}
var p = new Proxy(func,handler)
new p(1)  // {b: 10}

12.2.6 deleteProperty

deleteProperty方法用于拦截delete操作,返回false则无法删除

var target = {a: 1,_a: 2}
var handler = {
    deleteProperty: function(target,key){
        if(key[0] === '_'){
            return false
        }
    }
}
var proxy = new Proxy(target,handler)
console.log(delete proxy._a) // false(无法删除)
console.log(proxy) // Proxy {a: 1, _a: 2}

12.2.7 defineProperty()

defineProperty方法拦截了Object.defineProperty操作

var target = {a: 1,_a: 2}
var handler = {
    defineProperty: function(target,key,descriptor){
        if(key[0] === '_'){ // 某些属性不能添加
            return false
        }
        return Object.defineProperty(target,key,descriptor)
    }
}
var proxy = new Proxy(target,handler)
proxy.b = 2
proxy._b = 2;
console.log(proxy) // Proxy {a: 1, _a: 2, b: 2}(_b属性无法添加)

12.2.8 getOwnPropertyDescriptor()

getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor(),返回一个属性对象或者undefined

var target = {a: 1,_a: 2}
var handler = {
    getOwnPropertyDescriptor: function(target,key){
        if(key[0] === '_'){
            return;
        }
        return Object.getOwnPropertyDescriptor(target,key)
    }
}
var proxy = new Proxy(target,handler)
console.log(Object.getOwnPropertyDescriptor(proxy,'_a')) // undefined
console.log(Object.getOwnPropertyDescriptor(proxy,'a')) // {value: 1, writable: true, enumerable: true, configurable: true}

12.2.9 getPrototypeOf()

getPrototypeOf方法主要用来拦截获取对象原型。包括以下操作: Object.prototype._proto_Object.prototype.isPrototypeOf()Object.getPrototypeOf().Reflect.getPrototypeOfinstanceOf

var target = {a: 1,_a: 2}
var handler = {
    getPrototypeOf: function(target,key){
        return target
    }
}
var proxy = new Proxy(target,handler)
console.log(proxy.__proto__ === target); // true
console.log(target.isPrototypeOf(proxy)) // true
console.log(Object.getPrototypeOf(proxy) === target) // true
console.log(Reflect.getPrototypeOf(proxy) === target) // true

12.2.10 isExtensible()

isExtentsible方法拦截Object.isExtensible操作

var p = new Proxy({},{
    isExtensible: function(target){
        console.log('jll');
        return true;
    }
})
Object.isExtensible(p) 
// jll
// true

12.2.11 ownKeys()

ownKeys方法用来拦截对象自身属性的读取操作。包括: Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() 例子:

var target = {
    a: 1,
    _a: 1,
    [Symbol('a')]: 1,
    [Symbol('_a')]: 1
}
var handler = {
    ownKeys(obj){
        return ['a'] // 不返回数组会报错
    }
}
var proxy = new Proxy(target,handler);
console.log(Object.keys(proxy)) // ['a']
console.log(Object.getOwnPropertyNames(proxy)) // ['a']
console.log(Object.getOwnPropertySymbols(proxy)) // []

不可拓展时必须原样返回

12.2.12 preventExtensions

preventExtensions方法拦截Object.preventExtentsions()

var target1 = {}
var handler = {
    preventExtensions(target){
        console.log('jll');
        Object.preventExtensions(target);
        return true; // 必须返回
    }
}
var proxy = new Proxy(target1,handler);
Object.preventExtensions(proxy)
// jll
// true

12.2.13 setPrototypeOf()

setPrototypeOf方法主要用于拦截Object.setPrototypeOf方法

var target1 = {}
var handler = {
    setPrototypeOf(target,proto){
        throw new Error('不能修改原型对象')
    }
}
var proxy = new Proxy(target1,handler);
Object.setPrototypeOf(proxy,target1) // Uncaught Error: 不能修改原型对象

12.3 Proxy.revocable()

Proxy.revocable方法返回一个可取消的Proxy实例

let target = {};
let handler = {};
let {proxy,revoke} = Proxy.revocable(target,handler)
proxy.a = 123;
console.log(proxy.a) // 123
revoke(); // 取消后无法访问
proxy.foo; // 报错 Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

12.4 this问题

Proxy代理情况下,this关键字会指向Proxy代理。

var target = {
    checkBind: function (){
        console.log(this === proxy)
    }
}
var handler = {};
const proxy = new Proxy(target,handler);
proxy.checkBind(); // true

使用WeakMap结合对象存储时,会发生指向错误,导致取不到值

var _name = new WeakMap();
class Person{
    constructor(name){ // 实例化时设置WeakMap键值对
        _name.set(this,name)
    }
    get name(){
        return _name.get(this)
    }
}
const lee = new Person('lee')
console.log(lee.name) // lee
const proxy = new Proxy(lee,{})
console.log(proxy.name) // undefined(由于this指向Proxy对象导致无法取到WeakMap里面的值)

原生对象内部属性必须通过正确的this获取

var target = new Date();
var proxy = new Proxy(target,{});
proxy.getDate(); // 报错 Uncaught TypeError: this is not a Date object.

可以通过重新绑定this指向解决

var target = new Date();
var proxy = new Proxy(target,{
    get(target,property){
        if(property === 'getDate'){
            return target.getDate.bind(target); // 重新指定this
        }
        return Reflect.get(target,property)
    }
});
proxy.getDate();// 22

第13章 Reflect

13.1 概述

ES6提供得到操作对象的API。 1、将Object对象一些明显属于语言内部的方法放到Reflect上 2、修改Object返回结果,使其更加合理,例如Reflect.defineProperty(target,property,attributes)会返回false,在Object上只会抛出错误 3、让Object操作变成函数行为。例如,key in obj变为Reflect.has(obj,key)、delete obj[key]变为Reflect.deleteProperty(obj,key) 4、Reflect对象的方法与Proxy对象方法一一对应

var target = {};
var proxy = new Proxy(target,{
    get(target,property){ // Proxy对象get方法
        if(property === 'getDate'){
            return target.getDate.bind(target);
        }
        return Reflect.get(target,property) // Reflect的get方法
    }
});

13.2 静态方法

Reflect.apply(target,thisArg,args) Reflect.construct( target,args) Reflect.get(target,name,receiver) Reflect.set(target,name, value,receiver) Reflect.defineProperty( target,name,desc) Reflect.deleteProperty( target, name) Reflect.has( target,name) Reflect.ownKeys(target) Reflect.isExtensible(target) Reflect. preventExtensions( target) Reflect.getOwnPropertyDescriptor(target, name) Reflect.getPrototypeOf(target) Reflect.setPrototypeOf(target, prototype)

13.2.1 Reflect.get(target,name,receiver)

返回对象的属性值。

var obj = {
    a: 1, 
    b:2,
    get c(){
        return this.a + this.b;
    }
}
console.log(Reflect.get(obj,'a')) // 1
console.log(Reflect.get(obj,'b')) // 2
console.log(Reflect.get(obj,'c')) // 3

第三个参数绑定this指向

var obj = {
    a: 1, 
    b:2,
    get c(){
        return this.a + this.b;
    }
}
var receiverObj = {
    a: 3,
    b:3
}
console.log(Reflect.get(obj,'c',receiverObj)) // 6(this指向改变了)

第一个参数不是对象时报错

Reflect.get(1,'c',receiverObj) // 报错 Uncaught TypeError: Reflect.get called on non-object
Reflect.get({},'c',receiverObj) // undefined(不存在时返回undefined

13.2.2 Reflect.set(target,name,value,receiver)

设置对象的属性值

var obj = {
    a: 1,
    set b(value){
        return this.a = value;
    }
}
Reflect.set(obj,'b',2)
console.log(obj) // {a: 2}

第三个参数改变set函数this指向

var obj = {
    a: 1, 
    set a(value){
        return this.a = value;
    }
}
var receiverObj = {
    a: 2,
}
Reflect.set(obj,'a',3,receiverObj)
console.log(receiverObj) // {a: 3}(改变的是另一个对象,this指向改变)

第一个参数不是对象会报错

Reflect.set(1,'a',1) // 报错 Uncaught TypeError: Reflect.set called on non-object

Reflect.set会触发Proxy.defineProperty

var target = {};
var handler = {
    set(target,key,value,receiver){
        console.log('触发了set函数')
        Reflect.set(target,key,value,receiver)
    },
    defineProperty(target,key,attribute){
        console.log('触发了定义函数')
        Reflect.defineProperty(target,key,attribute)
    }
}
var proxy = new Proxy(target,handler)
Reflect.set(proxy,'a',1)
// 触发了set函数
// 触发了定义函数

13.2.3 Reflect.has(obj,name)

var obj={a:1};
console.log('a' in obj) // true(旧写法)
console.log(Reflect.has(obj,'a')) // true(新写法)

如果第一个参数不是对象报错

console.log(Reflect.has(1,'a')) // 报错 Uncaught TypeError: Reflect.has called on non-object

13.2.4 Reflect.deleteProperty(obj,name)

Reflect.deleteProperty方法删除属性

var obj={a:1,b:2};
delete obj.a; // 旧写法
Reflect.deleteProperty(obj,'b'); // 新写法
obj; // {}

13.2.5 Reflect.construct(tartget,args)

Reflect.construct调用构造函数,类似new创建对象

function Person(name,age){
    this.name = name;
    this.age = age;
}
var person1 = new Person('mht',30); // 旧写法
var person2 = Reflect.construct(Person,['lee',18]) // 新写法
console.log(person1,person2) // Person {name: "mht", age: 30} Person {name: "lee", age: 18}

13.2.6 Reflect.getPrototypeOf(obj)

获取对象的_proto_属性,对应Object.getPrototype(obj) function Person(){}

const person = new Person()
console.log(Object.getPrototypeOf(person) === Person.prototype) // ture(旧写法)
console.log(Reflect.getPrototypeOf(person) === Person.prototype) // true (新写法)

区别: Object.getPrototype(obj)会转换非对象为对象

console.log(Object.getPrototypeOf(1)) // Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
console.log(Reflect.getPrototypeOf(1)) // 报错 Uncaught TypeError: Reflect.getPrototypeOf called on non-object

13.2.7 Reflect.setPrototype(obj,newProto)

设置对象的_proto_属性,返回第一个一个参数对象,对应Object.setPrototypeOf(obj,newProto)

function Person(){} 
var person = new Person()
var proto1 = {a:1}
var proto2 = {b:2}
Object.setPrototypeOf(person,proto1)
console.log(person.__proto__ === proto1 ) // true
Reflect.setPrototypeOf(person,proto2)
console.log(person.__proto__ === proto2) // true

如果第一个参数是不是对象,Object.setPrototypeOf()会返回第一个参数,而Rflect.setPrototypeOf()会报错

Object.setPrototypeOf(1,{}) // 1
Reflect.setPrototypeOf(1,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object

第一个参数是null或者undefined,两者都会报错

Object.setPrototypeOf(null,{}) // 报错 Uncaught TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(undefined,{}) // 报错 Uncaught TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(undefined,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object
Reflect.setPrototypeOf(null,{}) // 报错 Uncaught TypeError: Reflect.setPrototypeOf called on non-object

13.2.8 Reflect.apply(func,thisArg,args)

Reflect.apply方法等同于Function.prototype.apply.call(func,thisArg,args) 旧写法

const ages = [1,2,3,4,5,6,7,8,9];
const youngest = Math.min.apply(Math,ages) // 实际上是把参数转换为数组
const oldest = Math.max.apply(Math,ages)
const type = Object.prototype.toString.call(youngest)
console.log(youngest,oldest,type) // 1 9 "[object Number]"

新写法

const ages = [1,2,3,4,5,6,7,8,9];
const youngest = Reflect.apply(Math.min,Math,ages)
const oldest = Reflect.apply(Math.max,Math,ages)
const type = Reflect.apply(Object.prototype.toString,youngest,[])
console.log(youngest,oldest,type) // 1 9 "[object Number]"

13.2.9 Reflect.defineProperty(target,propertyKey,attributes)

Rflect.defineProperty方法基本等同于Object.defineProperty,用来定于属性,后者会逐渐废除。

var obj = {};
// 旧写法
Object.defineProperty(obj,'a',{
    value: 1
})
// 新写法
Reflect.defineProperty(obj,'b',{
    value: 2
})
console.log(obj) // {a: 1, b: 2}

第一个参数不是对象会抛出错误

Reflect.defineProperty(1,'a') // 报错 Uncaught TypeError: Reflect.defineProperty called on non-object
    at Object.defineProperty (<anonymous>)

13.2.9 Reflect.getOwnPropertyDescriptor(target,propertyKey)

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,获取对象的描述

var obj = {};
Object.defineProperties(obj,{
    a: {
        value:1,
        writable: false
    },
    b:{
        value: 2,
        configurable: false
    }
})
// 旧写法
console.log(Object.getOwnPropertyDescriptor(obj,'a'))
// 新写法
console.log(Reflect.getOwnPropertyDescriptor(obj,'a'))

如果第一个参数不是对象

Object.getOwnPropertyDescriptor(1,'a') // undefined(返回undefined)
Reflect.getOwnPropertyDescriptor(1,'a') // 报错 Uncaught TypeError: Reflect.getOwnPropertyDescriptor called on non-object

13.2.11 Reflect.isExtensible(target)

Reflect.isExtentsible方法对应Object.isExtensible

var obj = {};
console.log(Object.isExtensible(obj)) // true
console.log(Reflect.isExtensible(obj)) // true

如果第一个参数不是对象

console.log(Object.isExtensible(1)) // false
console.log(Reflect.isExtensible(1)) // 报错 Uncaught TypeError: Reflect.isExtensible called on non-object

13.2.12 Reflect.preventExtensions(target)

Reflect.preventExtensions对应Object.preventExtensions方法,用于使一个对象变为不可拓展的。

var obj1 = {};
var obj2 = {};
console.log(Object.preventExtensions(obj1)) // {}
console.log(Reflect.preventExtensions(obj2)) // true(与上面的返回值不一样)

第一个参数不是对象时

console.log(Object.preventExtensions(1)) // 1
console.log(Reflect.preventExtensions(1)) // 报错 Uncaught TypeError: Reflect.preventExtensions called on non-object

13.2.13 Rflect.ownKeys(target)

Reflect.ownKeys方法适用于返回对象的所有属性

var obj = {
    a:1,
    b:2,
    [Symbol('a')]: 1,
    [Symbol('b')]: 2,
}
console.log(Object.getOwnPropertyNames(obj)) // ["a", "b"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(a), Symbol(b)]
console.log(Reflect.ownKeys(obj)) // ["a", "b", Symbol(a), Symbol(b)]

第14章 Promise对象

14.1 Promise的含义

两个特点: 1、状态不受外界影响。2、一旦状态改变旧不会再变,任何时候都是这个结果。 缺点: 1、无法取消2、不设置回调函数无法将Promise错误反应到外部3、pending状态无法得知目前进展到哪一个阶段。

14.2 基本用法

var promise = new Promise(function(resolve,reject){
    // ...some code
    if(/*异步操作成功*/){
        resolve(value)
    }else{
        reject(error)
    }
})
promise.then(function(value){
    // success
},function(error){
    // failure
})

例子

var promise = new Promise(function (resolve,reject){
    setTimeout(()=>resolve('这个是传递的参数'), 1000)
})
promise.then(function(value){
    console.log(value)
})
// 1秒后输出:这个是传递的参数

何时resolve何时调用对应的成功回调,何时reject何时调用错误回调 立即resolve就会立即调用成功回调函数,reject同理

var promise = new Promise(function (resolve,reject){
    resolve('这个是传递的参数')
})
promise.then(function(value){
    console.log(value)
}) // 输出: 这个是传递的参数
Promise.resolve('这个是传递的参数').then((value)=>{
    console.log(value) 
}) //  这个是传递的参数

reject函数参数一般是Error对象实例,表示抛出错误。resolve函数的参数可以是正常参数也可以是Promise实例

var p1 = new Promise(function(resolve,reject){
    resolve('成功的参数')
})
var p2 = new Promise(function(resolve,reject){
    resolve(p1)
})
p2.then(value =>{console.log(value)}) // 成功的参数

p1的状态决定p2的状态。

var p1 = new Promise(function(resolve,reject){
    reject('失败的参数')
})
var p2 = new Promise(function(resolve,reject){
    resolve(p1)
})
p2.then(value =>{console.log(value)},error=>{console.log(error)}) // 失败的参数

调用resolve和reject不会立即终结函数的执行

var p = new Promise(function(resolve,reject){
    resolve('传递的参数')
    console.log('这里可以继续执行')
})
p.then(value=>{console.log(value)}) 
// 这里可以继续执行
// 传递的参数

如果想要终结呢?增加return

new Promise(function(resolve,reject){
    return resolve('传递的参数')
    console.log('这里可以继续执行') // 这里被终结了
}).then(value=>{console.log(value)}) 
// 传递的参数

14.3 Promise.prototype.then()

Promise实例具有then方法,即then方法是定义在原型对象上,为Promise实例添加状态改变时的回调函数。then方法返回一个新的Promise实例(不是原来的Promise实例),因此可以采用链式写法。

new Promise(function(resolve,reject){
    resolve('传递的参数1')
}).then(value=> '传递的参数2')
.then(value=> {console.log(value)}) // 传递的参数2

14.4 Promise.prototype.catch()

指定发生错误时的回调函数 可以采用不同的方式接受错误

new Promise(function(resolve,reject){
    throw new Error('这是个错误')
}).catch(error=>{console.log(error)}) // Error: 这是个错误

相当于

new Promise(function(resolve,reject){
    throw new Error('这是个错误')
}).then(null,error=>{console.log(error)}) // Error: 这是个错误

采用不同的方式抛出错误

new Promise(function(resolve,reject){
    throw new Error('这是个错误')
}).catch(error=>{console.log(error)}) // Error: 这是个错误
new Promise(function(resolve,reject){
    reject(new Error('这是个错误'))
}).then(null,error=>{console.log(error)})  // Error: 这是个错误

状态已经变为resolved,再抛出错误是无效的。

new Promise(function(resolve,reject){
    resolve('成功的参数')
    throw new Error('这是个错误') // 无效
}).then(value=>{console.log(value)})
.catch(error=>{console.log(error)})
// 成功的参数

Promise对象的错误具有“冒泡”性质,会一直向后传递。

Promise.resolve('这是传递的参数').then(()=>{
    throw new Error('这是一个报错')
}).then(()=>{
    // 随便写点什么
}).catch((error)=>{
    console.log(error)
}) 
// Error: 这是一个报错(错误总会被捕获)

建议:使用catch捕获错误,而不是使用then的第二个回调参数。 那么没有catch捕获错误时会怎么样?

Promise.resolve().then(()=>{
    console.log(a+2)
})
// 报错 Promise {<rejected>: ReferenceError: a is not defined ...}(但不会终止脚本执行)

promise中使用setTimeout

var p = new Promise(function(resolve,reject){
    resolve('这是传递的参数')
    setTimeout(()=>{
        throw new Error('抛出错误')
    })
})
p.then((value)=>{
    // console.log(value)
}).catch((err)=>{
    console.log(err)
})
// 报错 Uncaught Error: 抛出错误(这个错误无法被catch捕获)

如果catch方法也是可以抛出的,尽量不要进行复杂操作

var p = new Promise(function(resolve,reject){
    throw new Error('错误')
})
p.catch((err)=>{
    console.log(x+1)
}) 
// 报错 Uncaught (in promise) ReferenceError: x is not defined

14.5 Promise.all()

Promise.all方法用于将多个Promise实例包装成一个新的Promise实例 var p = Promise.all ([pl , p2 , p3]) ; // 参数必须具有iterator接口 全部实例状态都变为fulfilled,或者其中一个变为rejected,才会调用后面的回调函数。 全部通过的例子

var p1 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(1)
    },1000)
})
var p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(2)
    },2000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
    console.log(value1+value2)
})
// 3

其中一个为rejected的例子

var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('报错信息')
    },1000)
})
var p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(2)
    },2000000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
    console.log(value1+value2)
}).catch(err=>{
    console.log(err)
})
// 输出 报错信息(不用等待p2执行完)

如果实例本身也有catch方法,那么两个都会执行

var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('报错信息')
    },1000)
})
p1.catch(err=>{
    console.log(err)
})
var p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(2)
    },2000000)
})
Promise.all([p1,p2]).then(([value1,value2])=>{
    console.log(value1+value2)
}).catch(err=>{
    console.log(err)
})
// 报错信息
// 报错信息

14.6 Promise.race()

Promise.race将多个Promise实例包装成一个新的Promise实例。 var p = Promise.race ([pl , p2 , p3]) ;

var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('报错信息')
    },1000)
})
var p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(2)
    },2000000)
})
Promise.race([p1,p2]).then((value)=>{
    console.log(value)
}).catch(err=>{
    console.log(err)
})
// 打印输出 报错信息
var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('报错信息')
    },1000)
})
var p2 = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(2)
    },200)
})
Promise.race([p1,p2]).then((value)=>{
    console.log(value)
}).catch(err=>{
    console.log(err)
})
// 打印输出 2

14.7 Promise.resolve()

将对象转换为Promise对象

Promise.resolve('foo')
// 等价于
new Promise(resolve=>resolve('foo'))

情况一: 参数时一个Promise实例

如果参数是Promise实例,那么Promise.resolve直接返回
var p = new Promise((resolve)=>{
    resolve(1)
})
Promise.resolve(p).then(value=>{
    console.log(value)
})
// 1(Promise对象被直接返回)

情况二: 参数是一个thenable对象,即具有then方法的对象

var thenableObj = {
    then: function(resolve){
        resolve('1')
    }
}
Promise.resolve(p).then(value=>{
    console.log(value)
})
// 1(更改为then方法里面的resolve)

情况三:参数不是具有 then 方法的对象或根本不是对象

Promise.resolve(1).then(value=>{
    console.log(value)
}) // 1(直接返回)

情况四: 不带任何参数 会在事件循环结束时执行then回调方法,而setTimeout是下一轮事件循环执行

console.log('1')
setTimeout(()=>{
    console.log('3')
})
Promise.resolve().then(value=>{
    console.log('2')
})
// 1 2 3

14.8 Promise.reject()

返回一个状态为rejected的新的Promise实例。reject与resolve不同,只会原封不动的传递给reject回调函数。

Promise.reject('错误').then(null,err=>{
    console.log(err)
})
// 错误

14.9 finally()

Promise.resolve(1).finally(value =>{console.log(value)}) // 1

第15章 Iterator和for...of循环

15.1 Iterator遍历器概念

只要部署了Iterator接口就可以完成遍历操作,一般是供for...of消费。每次调用next方法都会返回数据结构的信息。let arr = [1,2];

let it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}

15.2 默认Iterator接口

只要具有Symbol.iterator属性,就认为是可以遍历的,会调用这个方法返回结果,作为属性值必须放在方括号中。

var obj ={
    [Symbol.iterator]: function(){
        return {
            next:function(){
                return {
                    value: 1,
                    done: true
                }
            }
        }
    }
}
var it = obj[Symbol.iterator]();
it.next(); // {value: 1, done: true}

原生具备Iterator接口数据结构如下 Array Map Set String TypeArray 函数的arguments对象 NodeList对象

let it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}

对象需要通过Symbol.iterator属性手动指定

var obj ={
    value: 1,
    [Symbol.iterator]: function(){
        return this // 巧妙地使其调用自身的next方法,进而可以获取正确的this指向
    },
    next:function(){
        if(this.value< 3){
            return {
                value: this.value++,
                done: false
            }
        }else{
            return {
                value: this.value,
                done: true
            }
        }
    }
}
var it = obj[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}

类数组遍历

var arrayLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for(var item of arrayLike){
    console.log(item)
} // a b c

当Symbol.iterator方法对应的不是遍历器生成函数,不返回一个遍历器对象是,解析引擎会报错。

var obj = {};
obj[Symbol.iterator] = ()=>{}
console.log([...obj]) // 报错 Uncaught TypeError: Result of the Symbol.iterator method is not an object

与while遍历结合

let arr = [1,2,3,4]
let it = arr[Symbol.iterator]();
let result = it.next()
while(!result.done){
    console.log(result.value)
    result = it.next()
} // 1 2 3 4

15.3 调用Iterator接口的场合

解构赋值 对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法

let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
let [first,...rest] = set;
console.log(x,y,first,rest) // a b a ["b", "c"]

拓展运算符 ...扩展运算符也会默认调用Iterator接口

var str = 'lee';
console.log([...str]) // ["l", "e", "e"]

yield* yield* 后面跟的是一个可遍历结构,会调用该结构的遍历器接口

var generator = function * (){
    yield 1;
    yield* 'lee'; // 字符串具有遍历接口
    yield 5;
}
var it = generator();
it.next() // {value: 1, done: false}
it.next() // {value: "l", done: false}
it.next() // {value: "e", done: false}
it.next() // {value: "e", done: false}
it.next() // {value: 5, done: false}
it.next() // {value: undefined, done: true}

其他场合 for of

15.4 字符串的Iterator接口

[...('lee')] // ["l", "e", "e"]
var str = 'lee';
var iterator = str[Symbol.iterator]();
iterator.next() // {value: "l", done: false}

15.5 Iterator接口与Generator函数

Symbol.iterator方法最简单的实现是使用Generator函数

var myIterable = {
    *[Symbol.iterator](){
        yield 1;
        yield 2;
    }
};
console.log([...myIterable]) // 1 2

15.6 遍历器对象的return()、throw()

遍历器对象除了next方法,还有throw方法和return方法 for of循环提前退出(出错或者遇到break、continue)会调用return方法

var obj ={
    value: 1,
    [Symbol.iterator]: function(){
        return this // 巧妙地使其调用自身的next方法,进而可以获取正确的this指向
    },
    next:function(){
        if(this.value< 3){
            return {
                value: this.value++,
                done: false
            }
        }else{
            return {
                value: undefined,
                done: false
            }
        }
    },
    return(){
        console.log('进入return了')
        return {value: undefined,done: true}
    }
}
for(let value of obj){
    if(value == 2){
        break;
    }
}
// 进入return了

15.7 for of循环

for of循环内部调用的是数据结构的Symbol.iterator方法

15.7.1 数组

数组原生具备iterator接口

var arr = [1,2,3,4];
for(let value of arr){
    console.log(value)
} // 1 2 3 4

15.7.2 Set和Map结构

Set、Map结构原生具有Iterator接口,可以直接使用for...of循环

var set = new Set([1,2,3]);
console.log([...set]) // [1, 2, 3]
var map = new Map([[1,'a'],[2,'b']])
console.log([...map]) // [[1,'a'],[2,'b']]
for(var arr of map){
    console.log(arr)
} // [1, "a"] [2, "b"]
for(var [key,value] of map){
    console.log(key,value)
}
// 1 "a"
// 2 "b"

15.7.3 计算生成的数据结构

entries()、keys()、values() // ES6数组、Set、Map都部署了这个三个方法 Map结构的iterator接口默认就是调用entries方法。

let map = new Map([[1,"a"],[2,"b"]])
for(var [key,value] of map){
    console.log(key,value)
} 
// 1 "a"
// 2 "b"
var set = new Set([1,2,3,4])
for(let key of set.keys()){
    console.log(key)
} // 1 2 3 4

15.7.4 类数组的对象

字符串、DOM NodeList对象、arguments对象 字符串(可以正确识别 32 UTF-16 字符)

var str = 'lee';
console.log([...str]) // ["l", "e", "e"]

DOM NodeList对象

var nodeList = document.querySelectorAll('span')
console.log([...nodeList]) // 略

arguments对象

function add(){
    console.log([...arguments])
}
add(1,2,3,4) // [1, 2, 3, 4]

不具备Iterator接口的类数组对象,先用Array.from方法将其转换

var arrayLike = {length: 2,0:'a',1:'b'}
for(let key of Array.from(arrayLike)){
    console.log(key)
} // a b

15.7.5 对象

遍历对象 方法一:

var obj ={
    a: 1,
    b: 2
}
for(var key of Object.keys(obj)){
    console.log(key)
} // a b

方法二:

var obj ={
    a: 1,
    b: 2
}
function* entries(){
    for(let [key,value] of Object.entries(obj)){
        yield [key,value]
    }
}
for(var [key,value] of entries(obj)){
    console.log(key,value)
}
// a 1
// b 2

15.7.6 与其他遍历比较

for in会返回包括原型链上的键名

var obj={
    a: 1,
    b:2
}
Object.setPrototypeOf(obj,{c: 3})
for(let key in obj){
    console.log(key,obj[key])
}
// a 1
// b 2
// c 3(原型链上的也返回了)

for循环写起来比较繁琐 forEach

[1,2,3].forEach(item=>{
    console.log(item);
    return; // 问题无法跳出循环
})

for of 优点: 可以与break和continue、return搭配使用

var obj={
    a: 1,
    b:2
}
Object.setPrototypeOf(obj,{c: 3})
for(let key of Object.keys(obj)){
    console.log(key,obj[key])
}
// a 1
// b 2(没有访问原型链上的键)

第16章 Generator函数的语法

16.1 简介

16.1.1基本概念

执行Generator函数会返回一个遍历器对象,封装了多个状态的状态机。 两个特征: 一是function命令和函数名之间有一个星号;二是函数体内使用yield语句定义不同的内部状态。

function *iterator(){
    yield 1;
    yield 2;
    yield 3
}
const it = iterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}

16.1.2 yield表达式

function *iterator(){
    yield 1;
    return 2;
    yield 3
}
var it = iterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}(return 会打断yield产出)

不使用yield时

function *iterator(){
    console.log(2)
}
var it = iterator();
console.log(1)
it.next();
// 1
// 2(可以延迟执行)

yield用在其他地方时报错

function iterator(){
    yield 1
}
iterator(); // 报错 Uncaught SyntaxError: Unexpected number

yield不能用在forEach循环中

function *iterator(){
    [1,2,3,4].forEach(item=>{
        yield item;
    })
}
for(var key of iterator()){
    console.log(key)
}
// 报错 Uncaught SyntaxError: Unexpected identifier

应改为for循环

function *iterator(){
    var arr = [1,2,3,4];
    for(var i = 0;i<arr.length;i++){
        yield arr[i]
    }
}
for(var key of iterator()){
    console.log(key)
} // 1 2 3 4

yield在另一个表达式之中,必须放在圆括号内

function *example(){
    console.log('hello'+ yield 1)
}
var it = example();
it.next(); // 报错 Uncaught SyntaxError: Unexpected identifier

修改后

function *example(){
    console.log('hello'+ (yield 1))
}
var it = example();
it.next(); // {value: 1, done: false}
it.next(); // helloundefined {value: undefined, done: true}

yield表达式用作函数参数

function add(a,b){
    console.log(a+b)
}
function *example(){
    add(yield 1,yield 2)
}
var it = example();
it.next(); // {value: 1, done: false}

用于赋值表达式

function *example(){
    var a= yield;
    console.log(a)
}
var it = example();
it.next();

16.1.3 与Iterator接口的关系

Generator函数就是遍历生成函数,调用next方法时返回这些结构{value: undefined, done: false},因此可以赋值对象的Symbol.iterator属性。

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

16.2 next方法的参数

function* iterator(x){
    var a = 2*(yield x)
    var b = yield(a/4);
    return a+b;
}
var it = iterator(4);
it.next(); // {value: 4, done: false}
it.next(); // {value: NaN, done: false}(因为2*undefine结果为NaNit.next(); // {value: NaN, done: false}
function* iterator(x){
    var a = 2*(yield x)
    var b = yield(a/4);
    return a+b;
}
var it = iterator(4);
it.next(); // {value: 4, done: false} (第一次传递参数不可用)
it.next(5); // {value: 2.5, done: false}
it.next(6); // {value: 16, done: true}

可以逐步传入参数,(yield x)相当于一个有产出的参数变量

16.3 for...of循环

for...of循环可以自动遍历Generator函数生成的Iterator对象,不需要调用next方法。

function *foo(){
    yield 1;
    yield 2;
    yield 3;
    return 4; // 这个不会遍历出来
}
for(let value of foo()){
    console.log(value)
}
// 1 2 3
console.log([...foo()]) // [1, 2, 3]
Array.from(foo()) // [1, 2, 3]
let [x,y] = foo()
console.log(x,y) // 1 2

16.4 Generator.prototype.throw()

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

var gen = function *(){
    try{
        yield 1;
    }catch(e){
        console.log(e,'内部1')
    }
    try{
        yield 2;
    }catch(e){
        console.log(e,'内部2')
    }
}
var it = gen();
it.next();
try{
    it.throw('捕获错误')
    it.throw('捕获错误')
}catch(e){
    console.log(e,'外部1')
}
it.next();
try{
    it.throw('捕获错误')
}catch(e){
    console.log(e,'外部2')
}
// 捕获错误 内部1
// 捕获错误 内部2
// 捕获错误 外部2

内部错误外部捕获

function* gen(){
    yield 1;
    throw new Error('错误')
}
var it = gen();
it.next(); // {value: 1, done: false}
try{
    it.next()
}catch(e){
    console.log(e)
}
// Error: 错误

16.5 Generator.prototype.return()

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

function *gen(){
    yield 1;
    yield 2;
    yield 3;
}
var g = gen();
console.log(g.next()) // {value: 1, done: false}
console.log(g.return('foo')) // {value: "foo", done: true}
console.log(g.next()) // {value: undefined, done: true}(已经被终结了)

return方法要在finally代码后执行

function *gen(){
    yield 1;
    try{
        yield 2;
    }finally{
        yield 3;
    }
}
var g = gen();
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.return('foo')) // {value: 3, done: false}
console.log(g.next()) // {value: "foo", done: true}

16.6 yield* 表达式

使用yield* 语句在Generator函数里面再执行另一个函数,不使用的话完全没有效果

function *gen1(){
    yield 1;
    yield *gen2()
    yield 3;
}
function *gen2(){
    yield 2;
}
var g = gen1();
console.log([...g]) // [1, 2, 3]
function *gen1(){
    yield 1;
    yield *'lee'
    yield 3;
}

var g = gen1();
console.log([...g]) // [1, "l", "e", "e", 3]

16.7 作为对象属性的Generator函数

let obj = {
    * gen(){
        //代码
    }
}

等同于

let obj = {
    gen: function *(){
        // 代码
    }
}

16.8 Generator函数this

function *gen(){
    this.a = 1;
}
gen.prototype.b = function(){
    return 'b'
}
var obj = gen();
console.log(obj instanceof gen) // true
console.log(obj.b()) // b(obj是gen函数实例,可以调用其原型链上的方法)
console.log(obj.a) // undefirned(返回的是遍历器对象,无法当作构造函数)

不能结合new操作符创建对象

function *gen(){
    this.a = 1;
}
var obj = new gen(); // 报错 Uncaught TypeError: gen is not a constructor

改造其为构造函数

function *Gen(a){
    this.a = a;
    yield this.b = 2;
    yield this.c = 3;
}
var f = Gen.call(Gen.prototype,1); // 作用域绑定到原型上
console.log(f.next()); // {value: 2, done: false}
console.log(f.next()); // {value: 3, done: false}
console.log(f.next()); // {value: undefined, done: true}
console.log(f.a); // 1
console.log(f.b); // 2
console.log(f.c); // 3

增加中间函数

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

第17章

第18章 async函数

18.1 含义

async函数是异步操作变得更加方便,是Generator函数语法糖。 1.内置执行器 2.更好的语义 3.更广的适用性 4.返回值是Promise

18.2 用法

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

var promise = new Promise((resolve)=>{
    setTimeout(()=>{
        resolve()
        console.log('即使延迟这里也优先执行')
    },1000)
})
async function asyncFunc(){
    await promise;
    console.log('函数体后面延迟执行')
}
asyncFunc().then(()=>{
    console.log('呜呜呜我最后执行')
})
// 即使延迟这里也优先执行
// 函数体后面延迟执行
// 呜呜呜我最后执行

如果想要往promise中添加参数,添加多一层函数

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

async函数多种使用形式

// 函数声明
async function foo(){}
// 函数表达式
const foo = async function (){}
// 对象方法
let obj = {async foo(){}}
obj.foo().then(...
// Class方法
class obj{
    async foo(){}
}
// 箭头函数
const foo = async ()=>{}

18.3 语法

难点错误处理机制

18.3.1 返回Promise对象

async函数返回一个Promise对象。return返回的值,会成为then方法回调函数的参数。

async function f(){
    return '这是then函数需要的参数'
}
f().then(v=>console.log(v))
// 这是then函数需要的参数

采用then方法的第二个回调函数处理错误

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

18.3.2 Promise对象的变化

内部异步操作执行完才会执行then方法指定回调函数,除非遇到错误或者return

18.3.3 await命令

await后面是一个Promise对象,不是的话会自动转换为Promise对象。

async function f(){
    return await 123
}
f().then(v=>console.log(v))
// 123

也可以用.catch方法接受错误,或者rejected状态的参数

async function f(){
    await Promise.reject('这是rejected参数')
}
f().then(v=>console.log(v)).catch((e)=>console.log(e))
// 这是rejected参数
async function f(){
    await obj.a
}
f().then(v=>console.log(v)).catch((e)=>console.log(e))
// ReferenceError: obj is not defined

只要其中一个Promise语句变为reject,整个async函数都会中断

async function f(){
    await Promise.reject('出错了')
    await Promise.resolve('这里不会执行') // 不会执行
}
f().catch(e=>console.log(e)) // 出错了

用try catch包裹避免出错的地方,跳过出错继续执行

async function f(){
    try{
        await Promise.reject('出错了')
    }catch(e){
        console.log(e)
    }
    return await Promise.resolve('这里可以执行了')
}
f().then(v=>console.log(v)).catch(e=>console.log(e))
// 出错了
// 这里可以执行了

另外一种解决方法Promise对象添加一个catch方法,处理前面可能出现的错误。

async function f(){
    await Promise.reject('出错了').catch(e=>console.log(e))
    return await Promise.resolve('这里可以执行了')
}
f().then(v=>console.log(v))
// 出错了
// 这里可以执行了

18.3.4 错误处理

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

async function f(){
    await Promise.resolve(obj.a)
}
f().then(v=>console.log(v)).catch(e=>console.log(e))
// 报错 ReferenceError: obj is not defined

防止出错用try catch包裹,或者调用catch方法

18.3.5 使用注意点

第一点:防止出错,try catch包裹或者调用catch方法 第二点:不存在继发关系最好让它们同时触发 错误示范

async function foo(){
    await new Promise((resolve)=>{
        setTimeout(()=>{
            console.log('这是没有继发关系的第一个await')
            resolve()
        },1000)
    })
    await new Promise((resolve)=>{
        setTimeout(()=>{
            console.log('这是没有继发关系的第二个await')
            resolve()
        },800)
    })
}
foo();
// 这是没有继发关系的第一个await
// 这是没有继发关系的第二个await(依然要等前面一个执行完毕)

正确示范一:使用Promise.all()

async function foo(){
    let await1 = new Promise((resolve)=>{
        setTimeout(()=>{
            console.log('这是没有继发关系的第一个await')
            resolve()
        },1000)
    })
    let await2 =new Promise((resolve)=>{
        setTimeout(()=>{
            console.log('这是没有继发关系的第二个await')
            resolve()
        },800)
    })
    await Promise.all([await1,await2])
}
foo();
// 这是没有继发关系的第二个await
// 这是没有继发关系的第一个await(如果异步操作时间短会更早结束)

正确示范二:

async function foo(){
    let p1 = function(){
        return new Promise((resolve)=>{
            setTimeout(()=>{
                console.log('这是没有继发关系的第一个await')
                resolve()
            },1000)
        })
    }
    let p2 = function(){
        return new Promise((resolve)=>{
            setTimeout(()=>{
                console.log('这是没有继发关系的第二个await')
                resolve()
            },800)
        })
    }
    let res1 = p1();
    let res2 = p2();
    let await1 = await res1;
    let await2 = await res2;
}
foo();
// 这是没有继发关系的第二个await
// 这是没有继发关系的第一个await

节约了时间,可同时执行两个异步操作。 第三点:await命令只能用于async函数中

18.5 其他异步处理方法的比较

18.6 实例