ES6part2

131 阅读26分钟

1.对象的扩展

1.1 属性的简洁表示法

大括号内可以直接写入变量和函数,作为对象的属性(key-value分别为变量名和变量值)和方法(不需要再加function()声明)。

用于函数返回一个对象时,就很方便。此方法不能用于构造函数。

1.2 属性名表达式

方法一:obj.foo = true;直接用标识符作属性名

方法二:obj['a'+'bc'] = 123;用表达式作为属性名(也可以用于定义方法)

ES5中如果使用字面量方式定义对象(使用大括号),只能使用方法一。

ES6则允许使用大括号时,使用方法二。

如果属性名表达式是一个对象,会被默认转成字符串。

image-20211118091538436.png

1.3 方法的name属性

因为函数有name属性,对象方法也是函数,所以也有name属性。但是有两种特殊情况:

  1. bind方法创造的函数,name属性返回bound加上原函数的名字:

image-20211118092228550.png

  1. Function构造函数创造的函数,name属性返回anonymous:

image-20211118092242038.png 当对象的方法是一个Symbol值时,name属性返回这个Symbol值的描述。

1.4 属性的可枚举性和遍历
1.4.1 可枚举性

对象的每个属性都有一个描述对象,可以通过Object.getOwnPropertyDescriptor(obj, key)方法来获取对应对象属性的描述对象。描述对象又有自己的属性,其中有一个enumerable属性,称为”可枚举性“ ,如果enumerable属性为false,则此时的key属性不可枚举,就会被以下操作忽略:

    1. for...in循环:只遍历对象自身和基础的可枚举的属性。(包括继承的属性,尽量使用Object.keys()代替)
    1. Object.keys():返回对象自身的所有可枚举的属性的键名。
    1. JSON.stringify():只串行化对象自身可枚举的属性。
    1. Object.assign():只拷贝对象自身的可枚举的属性。(ES6新增)

class的原型的方法都不可枚举。

1.4.2 属性的遍历
  1. for...in:循环遍历对象自身的和继承的可枚举属性(不包括Symbol属性)。
  2. Object.keys(obj):返回一个元素为对象自身(无继承)可枚举属性(不包括Symbol属性)键名的数组。
  3. Object.getOwnPropertyNames(obj):在Object.keys()基础上加上了不可枚举属性的键名。
  4. Object.getOwnPropertySybols(obj):返回一个元素为对象自身Symbol属性键名的数组。
  5. Reflect.ownKeys(obj):在Object.getOwnPropertyNames()的基础上加上了Symbol属性(字符串键名也可)的键名。返回的数组按照数值属性-字符串属性-Symbol属性排序。
1.5 super关键字

指向当前对象的原型对象。(只能用在对象的方法中)

image-20211118100506614.png avaScript内部,super.foo等同于Object.getPrototypeOf(this).foo<属性>/Object.getPrototypeOf(this).foo.call(this)<方法>。

1.6 对象的扩展运算符
1.6.1 解构赋值

对象的解构赋值用于从一个对象取出其自身所有可遍历但尚未被读取的属性,分配到指定的对象上。所有的key value都会被拷贝(浅拷贝)到新对象上。同前面章节的解构赋值,如果右边是undefined/null就会报错,而且解构赋值还是只能放在最后一个参数的位置。

1.6.2 扩展运算符

...用于取出参数对象的所有可遍历属性拷贝到当前对象中。字符串会转为类似数组的对象,而不是空对象。对象的扩展运算符等同于Object.assign()方法。

可以用于合并两个对象,如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

1.7 AggragateError错误对象

在一个错误对象里面,封装了多个错误。如果某一个操作,同时引发了多个错误,就可以抛出一个AggregateError错误对象把所有的错误都放在这个对象里。

AggregateError本身是一个构造函数,用来生成实例对象。AggregateErorr(errors[,message])

errors:数组,每个成员都是一个错位对象。

message:字符串,表示AggregateError抛出时的提示信息,该参数可选。

三个属性:

name:错误名称,默认为“AggregateError”。

message:错误的提示信息。

errors:数组,每个成员都是一个错误对象。

2.对象的新增方法

2.1 Object.is()

ES5中,比较两个值是否相等,只能用==和===来比较。前者的缺点是会自动转换数据类型,后者的NaN不等于自身,而且+0与-0相等。所以ES6中就提出了Object.is()方法,基于===改进,解决了NaN不等于自身且+0和-0相等的问题。

2.2 Object.assign()

用于对象合并。Objetc.assign(target, source1, source2...),如果同名的属性会被source对象覆盖。只有一个参数时,直接返回参数,如果不是对象则转为对象返回,同上undefined和null如果是target对象就会报错。

此方法拷贝(浅拷贝)的属性不包括继承属性和不可枚举的属性,包括Symbol属性。

同名属性不是添加,而是替换。

参数是数组时,把数组当成索引和索引对应的元素这种映射关系的对象来处理。

如果对象是一个取值函数,则先求值,再将函数名和值作为映射关系复制过去。

常见用途:

  1. 为对象添加属性。Object.assign(this,...)
  2. 为对象添加方法。
  3. 克隆对象。
  4. 合并多个对象。
  5. 为属性指定默认值。
2.3 Object.getOwnPropertyDescriptors()

ES5中,此方法的返回值是某个对象的描述对象。ES2017中,返回某个对象的所有自身属性。主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

2.4 __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()

__proto__属性: 用来读取或设置当前对象的原型对象。本质就是调用的Object.prototype. proto。如果一个对象本身部署了 __proto__属性,该属性的值就是对象的原型。

Object.setPrototypeOf() 与 __proto__相同,用来设置一个对象的原型对象,返回参数对象本身。ES6推荐用此方法来设置原型对象。

Object.getPrototypeOf() 与上面的方法配套,用来读取一个对象的原型。(不是对象转成对象,无法转成就报错)。

2.5 Object.keys(),Object.values(),Object.entries()

Object.keys(): 前面介绍过。返回对象自身的所有可枚举的属性的键名的数组。

Object.values(): 返回一个成员是参数对象自身所有可遍历属性的键值的数组。

Object.entries(): 返回一个成员是参数对象自身所有可遍历属性的键值对的数组。

2.6 Object.fromEntries()

Object.entries()的逆操作,将键值对数组转为对象。

3.运算符的扩展

3.1 指数运算符:**

此运算符是右结合不是左结合,例如x**y就是y的x次方。

也可写成y **= x。

3.2 链判断运算符:?.

obj?.prop:对象属性是否存在

obj?.[expr] :同上

func?.(...args): 函数或对象方法是否存在。

注意点:

  1. 短路机制,不满足条件则不会向下执行后面的语句。是statement1?undefined:statement2的原理。若找不到,则会返回undefined不会执行statement2了。
  2. 括号的影响:如果属性链有圆括号,则链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
  3. 报错场合:构造函数、运算符右侧有模板字符串、左侧是super、用于赋值运算符左侧。
  4. 右侧不得为十进制数值。要不然会按照三元运算符处理。
3.3 Null判断运算符:??

当运算符左侧的值为Null或undefined时,才返回右侧的值。本质是逻辑运算,和&&、||存在优先级的问题。

3.4 逻辑赋值运算符: ||=、&&=、??=

用于为变量或属性设置默认值。

image-20211118134514807.png

4 Symbol

4.1 概述

为了防止属性名的冲突,ES6引进了Symbol。该值由Symbol函数生成,而且是独一无二的不会与其他属性名发生冲突。Symbol函数不能使用new命令,因为生成的是一个原始类型的值(是一个新的数据类型)而不是对象,此外该函数可以接受一个字符串为参数作为对Symbol实例的描述。

Symbol函数的参数只是对当前值的描述,所有相同参数的Symbol函数的返回值不相等。

4.2 Symbol.prototype.description

因为将symbol值转换成String时还是会有symbol字符,为了更加方便的拿到其对应的描述,ES2019就提供了一个实例的属性description来直接获取。

4.3 作为属性名的Symbol

因为Symbol值时唯一的,所有将其定为属性名就可以防止因为同名属性被改写或覆盖。此时不可以用点运算符。

4.4 实例:消除魔法字符串

魔术字符串:在代码中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。(具体情况具体应用)

4.5 属性名的遍历

前面的某些遍历方法会忽略Symbol值,所以用Object.getOwnPropertySymbols()方法来获取所有的Symbol属性值。

4.6 Symbol.for(), Symbol.keyFor()

前者实现重新使用同一个Symbol值。先根据字符串搜索有没有对应的Symbol,有就直接使用,没有就新建一个并注册到全局。与Symbol()构建方法不同的地方是,for()会登记到全局环境供搜索,另一个则不会。

后者用来返回一个已登记的Symbol类型值的key。

4.7 实例:模块的singleton模式

Singleton模式指的是调用一个类,任何时候返回的都是同一个实例。

对于Node,模块文件可以看成一个类,为了保证每次执行此文件返回的实例相同,就要把实例放到顶层对象global中,加载对应的mod,但是某些代码会使修改后加载的mod脚本失真,此时就可以使用Symbol,保证这个实例不会被覆盖但是可以被改写,而且外部无法引用这个值。

4.8 内置的Symbol值
4.8.1 Symbol.hasInstance:这个属性指向一个内部方法。被instanceof()调用判断某个对象是否为该对象的实例。

image-20211118141233761.png

4.8.2 Symbol.isConcatSpreadable:这个属性表示该对象用于Array.prototype.concat()时是否可以展开。

image-20211118141344386.png

4.8.3 Symbol.species:指向构造函数,创建衍生对象时会使用该属性返回的函数作为构造函数。
4.8.4 Symbol.match: 指向一个函数,str.match(myObject)会在这个属性存在时调用它,返回该方法的返回值。
4.8.5 Symbol.replace:指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
4.8.6 Symbol.search:指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
4.8.7 Symbol.split:指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
4.8.8 Symbol.iterator:对象的Symbol.iterator属性,指向该对象的默认遍历器方法。
4.8.9 Symbol.toPrimitive:指向一个方法,该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
4.8.10 Symbol.toStringTag:指向一个方法,在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型.

image-20211118141940345.png

4.8.11 Symbol.unscopables:指向一个对象,该对象指定了使用with关键字时,哪些属性会被with环境排除。

5.Set和Map数据结构

5.1 Set

Set是一个构造函数,用来生成数据结构。通过add()计入成员,而且重复的值不会被重复添加。

Set()可以用数组作为参数进行初始化,也可以用来去除数组重复成员[...new Set(array)],或者去除字符串里的重复字符[...new Set('ababbc')].join('')。

add()不会发生类型转换。

Set实例的属性和方法:

属性:

Set.prototype.constructor: 构造函数,默认就是Set函数。

Set.prototype.size:返回Set实例的成员总数。

方法:

Set.prototype.add(value):返回Set结构。

Set.prototype.delete(value):删除value,返回值为布尔值。

Set.prototype.has(value):判断该值是否在Set内,返回值为布尔值。

Set.prototype.clear():清除所有成员。

遍历操作:

Set.prototype.keys()/values()/entries()/forEach():返回键名/键值/键值对/遍历每个成员。

前三个都是返回遍历对象,最后一个是一个回调函数对每一个成员执行某种操作。

应用:与扩展运算符合用去重,数组的map和filter方法也可以间接用于Set,Set可以很容易的实现并集、交集、差集。

image-20211118151645688.png

5.2 WeakSet

与Set类似,区别是WeakSet的成员只能是对象,而且都是弱引用。有三个方法add(),delete(),has()不可以遍历。可以避免内存泄露。

5.3 Map

类似于对象的key-value映射,但是他的key不再局限于字符串,可以是各种类型的值。任何具有Iterator接口而且每个成员都是一个双元素的数组的数据结构都可以作为Map构造函数的参数,Set和Map都可以用来生成新的Map。

注意:如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

实例的属性和操作方法:

  1. size属性:返回成员总数
  2. Map.prototype.set(key,value):set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
  3. Map.prototype.get(key):g读取key对应的键值,如果找不到key,返回undefined。
  4. Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  5. Map.prototype.delete(key):法删除某个键,返回true。如果删除失败,返回false。
  6. Map.prototype.clear():清除所有成员,没有返回值。

遍历方法(和Set一样,遍历顺序就是插入顺序,应用同Set)

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

  1. Map->数组:[...map]
  2. 数组->Map:new Map([array1,array2])
  3. Map->对象:

image-20211118153226801.png

  1. 对象->Mapp:new Map(Object.entries(obj))

  2. Map->JSON:

    (1)Map的键名都是字符串:image-20211118153348730.png

    (2)Map的键名有非字符串:

image-20211118153428963.png 0. JSON->Map:

image-20211118153504648.png

特殊情况,数组成员中还有两个成员的数组:

image-20211118153546374.png

5.4 WeakMap

与Map结构类似,区别是只接受对象为键名,且键名指向的对象不计入垃圾回收机制。同WeakSet一样避免内存泄漏。

同样无遍历操作,也无size属性,只有四个方法:et()、set()、has()、delete()。

用于DOM节点作为键名的情况,当节点的状态发生改变就将新的状态作为键值放在WeakMap,如果DOM节点被删除,这个状态就会自动消失,不存在内存泄漏。

5.5 WeakRef

ES2021提供了WeakRef对象,用于创建对象的弱引用。该实例对象有一个deref()方法,原始对象存在时该方法返回原始对象,不存在时返回undefined。

注意。 标准规定,一旦使用WeakRef()创建了原始对象的弱引用,那么在本轮事件循环(event loop),原始对象肯定不会被清除,只会在后面的事件循环才会被清除。

5.6 FinalizationRegistry

ES2021新增,在某个目标对象被垃圾回收机制清除以后,就会执行这个回调函数。

6.Proxy

6.1 概述

用于修改某些操作的默认行为,属于一种”元编程“,对编程语言进行编程。

var proxy = new Proxy(target, handler):

new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

若handler为空,proxy就等同于target。

一个拦截器函数,可以设置拦截多个操作。

6.2 Proxy实例的方法

get(target, propKey, receiver): 拦截对象属性的读取,比如proxy.foo和proxy['foo']。

set(target, propKey, value, receiver): 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

has(target, propKey): 拦截propKey in proxy的操作,返回一个布尔值。

deleteProperty(target, propKey): 拦截delete proxy[propKey]的操作,返回一个布尔值。

ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

defineProperty(target, propKey, propDesc): 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。

preventExtensions(target): 拦截Object.preventExtensions(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)。

6.3 Proxy.revocable()

返回一个可取消的proxy实例。用于目标对象不允许直接访问必须通过代理访问,一旦访问结束就收回代理权,不再允许访问的情况。(只能通过代理访问一次)。

6.4 this问题

在Proxy代理情况下,目标对象内部的this指向Proxy代理。Proxy 拦截函数内部的this,指向的是handler对象。

有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。

6.5 实例:web服务的客户端

web接口返回各种数据时,Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

7.Reflect

7.1 概述

目的:

  1. 语言内部的新方法都将部署到Reflect对象上。
  2. 优化Object方法的返回结果。
  3. 将Object的所有操作变为函数操作。
  4. 与Proxy的方法一一对应,让Proxy对象更方便调用Reflect方法。
7.2 静态方法

Reflect.apply(target, thisArg, args):同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。

Reflect.construct(target, args):等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

Reflect.get(target, name, receiver):查找并返回target对象的name属性,如果没有该属性,则返回undefined。

Reflect.set(target, name, value, receiver):设置target对象的name属性等于value。

Reflect.defineProperty(target, name, desc):同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。

Reflect.deleteProperty(target, name):同于delete obj[name],用于删除对象的属性。

Reflect.has(target, name):对应name in obj里面的in运算符。

Reflect.ownKeys(target):用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。

Reflect.isExtensible(target):对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。

Reflect.preventExtensions(target):对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。

Reflect.getOwnPropertyDescriptor(target, name):同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。

Reflect.getPrototypeOf(target):用于读取对象的 __ proto __ 属性,对应Object.getPrototypeOf(obj)。

Reflect.setPrototypeOf(target, prototype):用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法

7.3 实例:使用Proxy实现观察者模式

image-20211118162215368.png

8.Promise对象

8.1 Promise 的含义

异步编程的一种解决方案。是一个封装了某个未来才会结束的事件的容器。

特点:

  1. 对象状态不受外界影响,只有pending、fulfilled、和rejected状态,只有异步操作的结果可以决定当前状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

后用resolved来指fulfilled状态。

8.2基本用法
const promise = new Promise(function(resolve, reject) {
  // ... some code
​
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

function也可以简写为箭头函数

8.3 Promise.prototype.then()

作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。该函数的返回值是一个新的Promise实例。可以链式调用

8.4 Promise.prototype.catch()

是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。返回值是一个新的Promise实例。此方法中可以继续抛出错误。

8.5 Promise.prototype.finally()

ES2018引进,无论promise是什么状态,最后都会执行的操作。

8.6 Promise.all()

将多个Promise实例封装为一个新的Promise实例。只有传入的Promise对象全部都是fufilled状态,才会调用Promise.all方法后的回调函数。

image-20211118164439698

8.7 Promise.race()

image-20211118164507121

8.8 Promise.allSettled()

在Promise.all()上的改进,只要所有Promise对象的状态都发生是改变了,返回的Promise对学校就会发生状态变更。

8.9 Promise.any()

ES2021引进,只要有一个实例时fulfilled这个封装实例就会变成fulfilled,只有所有实例都是rejected时状态才会是rejected。

8.10 Promise.resolve()

将现有对象转为Promise对象。

Promise.resolve('foo')就是new Promise(resolve => resolve('foo'))

参数情况:

  1. 参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  2. 参数是thenable对象,Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
  3. 如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
  4. Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
8.11 Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

8.12 应用

加载图片:

image-20211118165206504.png Generator函数和Promise结合:

image-20211118165259582.png

8.13 Promise.try()

解决不知道或者不想区分函数是同步函数还是异步操作,但是想用 Promise 来处理它的问题。让同步函数同步执行,异步函数异步执行。

9. Iterator和for...of循环

9.1 Iterator(遍历器)的概念

它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

作用

  1. 是为各种数据结构,提供一个统一的、简便的访问接口;
  2. 是使得数据结构的成员能够按某种次序排列;
  3. 是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

遍历过程: 用一个指针对象来指向当前数据结构的起始位置,每访问一次指针就后移一次。通过指针对象的next()方法来实现移动。

9.2 默认 Iterator 接口

一个数据结构如果有Symbol.iterator属性,就是可遍历的。

原生具备该接口的数据结构有:Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象。

9.3 调用 Iterator 接口的场合

前面介绍过的解构赋值、扩展运算符、yield*(后面跟一个可遍历的结构,他会调用该结构的遍历器接口)、其他场合。

9.4 字符串的 Iterator 接口

字符串就是一个类似数组的对象,也有原生接口。但是可以覆盖原生的Symbol.iterator方法来,修改遍历器的行为。

9.5 Iterator 接口与 Generator 函数

Symbol.iterator()最简单的实现就是Generator函数。

9.6 遍历器对象的 return(),throw()

前者是for...of循环提前退出,后者是配合Generator函数使用。

9.7 可以使用for...of 循环的数据类型

数组: for...of循环可以代替数组实例的forEach方法。Set和Map结构、计算生成的数据结构、类似数组的对象、对象。

for...in的缺点:

数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。

for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。

某些情况下,for...in循环会以任意顺序遍历键名。

for...of的优点:

有着同for...in一样的简洁语法,但是没有for...in那些缺点。

不同于forEach方法,它可以与break、continue和return配合使用。

提供了遍历所有数据结构的统一操作接口。

10.Generator函数的语法

10.1 简介

一个封装了多个内部状态的状态机,用来解决异步编程。

特征: function关键字和函数名直接有一个星号,函数体内部使用yield表达式定义不同的内部状态。

Generator函数调用后并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。然后调用遍历器对象的next方法,使指针下移。

10.2 next 方法的参数

带一个参数作为上一个yield表达式的返回值。

10.3 for...of 循环

此循环可以自动遍历Generator函数运行时生成的Iterator对象,此时不需要next方法。

10.4 Generator.prototype.throw()

在函数体外抛出错误,在函数体内捕获然后处理。

10.5 Generator.prototype.return()

函数返回的遍历器对象,可以用return方法返回一个给定的值。若不给参数,返回值的value属性为undefined。

10.6 next()、throw()、return() 的共同点

都是为了让Generator函数恢复执行,只是第一个是将yield表达式替换成一个值,第二个时将yield表达式替换成一个throw语句,第三个是将yield表达式替换为return语句。

10.7 yield* 表达式

解决Generator函数内部,调用另一个Generator函数需要在前者的函数体内手动完成遍历的问题。

10.8 作为对象属性的 Generator 函数
let obj = {
  * myGeneratorMethod() {
    ···
  }
};
10.9 Generator 函数的this

Generator函数返回的总是遍历器对象,拿不到this对象。想要解决这个问题就可以生成一个空对象,使用call方法绑定Generator函数内部的this。这样,构造函数调用后这个空对象就是Generator函数的实例对象。

10.10 含义

Generator与状态机:

ES5的状态机实现:

image-20211119094438973.png

通过Generator实现:

image-20211119094555173.png Generator与协程:

  1. 协程与子例程的差异:传统的子例程时堆栈式ide后进先出的执行方式,子函数都执行完毕才会结束执行父函数。而协程时多个线程可以并行执行,不过只有一个线程/函数处于正在运行的状态,其他都是暂停状态,不过线程间可以交换执行权。
  2. 协程与普通线程的差异:协程适用于多任务运行的环境,与普通的线程的区别就是同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个其他都处于暂停状态。

Generator与上下文:

Generator函数并不是根据运行的块级代码来采取堆栈式的执行顺序,而是一旦遇到yield命令,就会暂时退出堆栈,但不会消失,里面的所有变量和对象会冻结在当前状态,等到执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。

10.11 应用

异步操作的同步化表达: 因为Generator的暂停执行效果,可以把异步操作写在yield表达式里,等到调用next方法再往后执行,这样就不需要回调函数了。

控制流管理: 对Promise的链式调用优化,使用一个函数自动执行所有的步骤。但是这种做法只适合同步操作,不能有异步操作。

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.value
    scheduler(task);
  }
}

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

作为数据结构: 对任意表达式都提供类似数组的接口。

11.async函数

11.1 含义

本质就是Generator函数的语法糖,但是对Generator又有改进。

  1. 内置执行器:async函数会自动执行,不想Generator一样需要调用next放啊才能真正执行。
  2. 更好的语义:比起星号和yeild,语义更清楚。async表示函数里面有异步操作,await表示后面的表达式需要等待结果。
  3. 更广的适用性:yield命令后面只能是Thunk函数或者Promise对象,但async和await命令后面,可以是Promise对象和原始类型的值。
  4. 返回值时Promise。
11.2 基本用法

返回一个Promise对象,使用then方法添加回调函数,一旦遇到await就返回,异步操作完成后,再接着执行函数体内后面的语句。

// 函数声明
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 () => {};
11.3 语法

返回Promise对象: async函数内部return的值,就会成为这个Promise的then回调的参数。

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

Promise对象的状态变化: async返回的Promise在内部所有await命令后面的Promise对象执行完才会发生状态改变,除非遇到return或有错误被抛出。

await命令: 如果命令后面是个Promise对象,就返回该对象的结果,如果不是就返回对应的值。

错误处理: 如果await后面的异步操作出错,就等同于async函数返回的Promise被reject。为了避免出错,await指令一般放在try...catch代码块中。

使用注意点:

  1. 将await指令放在try...catch代码块中。
  2. 多个await指令如果不存在继发关系,最好同时触发。
  3. await只能在async函数内使用,在普通函数内会报错。
11.4 async 函数的实现原理

本质就是将Generator函数和自动执行器包装到一个函数里。

async function fn(args) {
  // ...
}
​
// 等同于function fn(args) {
  return spawn(function* () {
    // ...
  });
}
11.5 与其他异步处理方法的比较

相比Promise和async更加简洁,最符合语义。

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

Promise实现:

function logInOrder(urls) {
  // 远程读取所有URL
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });
​
  // 按次序输出
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

async实现:

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

加上await:

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });
​
  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
11.7 顶层 await

只用于ES6模块,不用于CommonJs模块,用来在async外部处理模块异步加载的问题。