ES6

85 阅读1小时+

ES6

├── 字符方法
│   ├── 实例方法:includes(), startsWith(), endsWith()
│   │     └─ includes       返回布尔值,表示是否找到了参数字符串。
│   │     └─ startsWith     返回布尔值,表示参数字符串是否在原字符串的头部。
│   │     └─ endsWith       返回布尔值,表示参数字符串是否在原字符串的尾部。
│   ├── 实例方法:repeat()
│   │     └─ repeat     方法返回一个新字符串,表示将原字符串重复`n`次。
│   ├── 实例方法:padStart(),padEnd()
│   │     └─ padStart   如果某个字符串不够指定长度,会在头部或尾部补全 用于头部补全
│   │     └─ padEnd     如果某个字符串不够指定长度,会在头部或尾部补全 用于头部补全
│   ├── 实例方法:trimStart(),trimEnd()
│   │     └─ trim       消除字符串所有的空格,
│   │     └─ trimStart  消除字符串头部的空格,
│   │     └─ trimEnd    消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
│   ├── 实例方法:matchAll()
│   │     └─ matchAll   方法返回一个正则表达式在当前字符串的所有匹配,详见《正则的扩展》的一章
│   ├── 实例方法:replaceAll()
│   │     └─ replaceAll 字符串的实例方法 `replace()`只能替换第一个匹配 引入了`replaceAll()`方法,可以一次性替换所有匹配。
│   ├── 实例方法:at()
│   │     └─ at         方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
├── 正则的扩展
├── 数值的扩展
│   ├── 数值分隔符
│   │     └─ 1_000 === 1000 ; 0.000_001 === 0.000001
│   ├── Number.isFinite(), Number.isNaN()
│   │     └─ Number.isFinite() 用来检查一个数值是否为有限的(finite),即不是 Infinity
│   │     └─ Number.isNaN() 用来检查一个值是否为 NaN
│   ├── Number.parseInt(), Number.parseFloat()
│   │     └─ 将全局方法`parseInt()``parseFloat()`,移植到`Number`对象上面,行为完全保持不变。
│   ├── Number.isInteger()
│   │     └─ `Number.isInteger()`用来判断一个数值是否为整数。
│   ├── Number.EPSILON
│   │     └─ ES6`Number`对象上面,新增一个极小的常量`Number.EPSILON`。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。 Number.EPSILON === Math.pow(2, -52)
│   │     └─ Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2)
│   ├── Number.isSafeInteger() 和 安全整数
│   │     └─ ES6 引入了`Number.MAX_SAFE_INTEGER``Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限
│   │     └─ `Number.isSafeInteger()`则是用来判断一个整数是否落在这个范围之内
│   ├── Math 对象的扩展
│   │     └─ Math.trunc()
│   │         └─ `Math.trunc`方法用于去除一个数的小数部分,返回整数部分。
│   │     └─ Math.sign()
│   │         └─ `Math.sign`方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
│   │     └─ Math.trunc()
│   │         └─ `Math.trunc`方法用于去除一个数的小数部分,返回整数部分。
│   │     └─ Math.cbrt()
│   │         └─ `Math.cbrt()`方法用于计算一个数的立方根。
│   │     └─ Math.fround()
│   │         └─ `Math.fround`方法返回一个数的32位单精度浮点数形式。
│   │     └─ Math.hypot()
│   │         └─ `Math.hypot`方法返回所有参数的平方和的平方根。
├── 函数的扩展
│   ├── 函数的 length 属性
│   │     └─ 指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
│   ├── 作用域
│   │     └─ 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
│   ├── rest 参数
│   │     └─ ES6 引入 rest 参数(形式为`...变量名`),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
│   │     └─ `arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.from`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。
│   ├── 严格模式
│   │     └─ ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
│   ├── name 属性
│   │     └─ 函数的`name`属性,返回该函数的函数名。
│   │     └─ `bind`返回的函数,`name`属性值会加上`bound`前缀。
│   ├── 箭头函数
│   │     └─ 基本用法
│   │         └─ ES6 允许使用“箭头”(`=>`)定义函数。
│   │     └─ 使用注意点
│   │         └─ 箭头函数没有自己的 `this`对象(详见下文)。
│   │         └─ 不可以当作构造函数,也就是说,不可以对箭头函数使用`new`命令,否则会抛出一个错误。
│   │         └─ 不可以使用 `arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
│   │         └─ 不可以使用 `yield`命令,因此箭头函数不能用作 Generator 函数。
│   │     └─ 不适用场合
│   │         └─ 由于箭头函数使得 `this`从“动态”变成“静态”,下面两个场合不应该使用箭头函数。
│   │         └─ 第一个场合是定义对象的方法,且该方法内部包括`this`。
│   │         └─ 第二个场合是需要动态 `this`的时候,也不应使用箭头函数。
│   │     └─ 嵌套的箭头函数
│   │         └─
│   ├── 尾调用优化
│   │     └─ 什么是尾调用?
│   │         └─ 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
│   │     └─ 尾调用优化
│   │         └─ 我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数`A`的内部调用函数`B`,那么在`A`的调用帧上方,还会形成一个`B`的调用帧。等到`B`运行结束,将结果返回到`A``B`的调用帧才会消失。如果函数`B`内部还调用函数`C`,那就还有一个`C`的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数`A`的内部调用函数`B`,那么在`A`的调用帧上方,还会形成一个`B`的调用帧。等到`B`运行结束,将结果返回到`A``B`的调用帧才会消失。如果函数`B`内部还调用函数`C`,那就还有一个`C`的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
│   │         └─ 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
│   │         └─ “尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
│   │         └─ 注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
│   │     └─ 尾递归
│   │     └─ 递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
├── 数组的扩展
│   ├── 扩展运算符
│   │     └─ 含义
│   │         └─ 扩展运算符(spread)是三个点(`...`)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
│   │     └─ 替代函数的 apply() 方法
│   │         └─ 由于扩展运算符可以展开数组,所以不再需要`apply()`方法将数组转为函数的参数了。
│   │     └─ 扩展运算符的应用
│   │         └─ (1)复制数组 数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
│   │         └─ (2)合并数组 扩展运算符提供了数组合并的新写法。
│   │         └─ (3)与解构赋值结合 扩展运算符可以与解构赋值结合起来,用于生成数组。
│   │         └─ (4)字符串 扩展运算符还可以将字符串转为真正的数组。
│   │         └─ (5)实现了 Iterator 接口的对象 任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
│   │         └─ ((6MapSet 结构,Generator 函数  扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
│   ├── Array.from()
│   │     └─ `Array.from()`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 SetMap)。
│   │     └─ `Array.from()`还可以接受一个函数作为第二个参数,作用类似于数组的`map()`方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
│   │     └─ 扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from()`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from()`方法转为数组,而此时扩展运算符就无法转换。
│   ├── Array.of()
│   │     └─ `Array.of()`方法用于将一组值,转换为数组。
│   ├── 实例方法:copyWithin()
│   │     └─ 数组实例的`copyWithin()`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
│   ├── 实例方法:find() 和 findIndex()
│   │     └─ 数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。
│   │     └─ 数组实例的`findIndex`方法的用法与`find`方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1`。
│   ├── 实例方法:fill()
│   │     └─ `fill`方法使用给定值,填充一个数组。 `fill`方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
│   ├── 实例方法:entries(),keys() 和 values()
│   │     └─ ES6 提供三个新的方法——`entries()``keys()``values()`——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用`for...of`循环进行遍历,唯一的区别是`keys()`是对键名的遍历、`values()`是对键值的遍历,`entries()`是对键值对的遍历。
│   ├── 实例方法:includes()
│   │     └─ `Array.prototype.includes`方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的`includes`方法类似。ES2016 引入了该方法。
│   ├── 实例方法:flat(),flatMap()
│   │     └─ 数组的成员有时还是数组,`Array.prototype.flat()`用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
│   │     └─ `flatMap()`方法对原数组的每个成员执行一个函数(相当于执行`Array.prototype.map()`),然后对返回值组成的数组执行`flat()`方法。该方法返回一个新数组,不改变原数组。
│   ├── 实例方法:at()
│   │     └─ const arr = [5, 12, 8, 130, 44]; arr.at(2) // 8
│   ├── 数组的空位
│   │     └─ 数组的空位指的是,数组的某一个位置没有任何值,比如`Array()`构造函数返回的数组都是空位。
│   │     └─ `forEach()`, `filter()`, `reduce()`, `every()``some()`都会跳过空位。
│   │     └─ `map()`会跳过空位,但会保留这个值
│   │     └─ `join()``toString()`会将空位视为`undefined`,而`undefined``null`会被处理成空字符串。
│   │     └─ `Array.from()`方法会将数组的空位,转为`undefined`,也就是说,这个方法不会忽略空
│   │     └─ 扩展运算符(`...`)也会将空位转为`undefined`。
│   ├── Array.prototype.sort() 的排序稳定性
│   │     └─ 排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
├── 对象的扩展
│   ├── 属性的简洁表示法
│   │     └─ 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
│   ├── 属性名表达式
│   │     └─ 方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。
│   ├── 方法的 name 属性
│   │     └─ 函数的`name`属性,返回函数名。对象方法也是函数,因此也有`name`属性。
│   │     └─ 如果对象的方法使用了取值函数(`getter`)和存值函数(`setter`),则`name`属性不是在该方法上面,而是该方法的属性的描述对象的`get``set`属性上面,返回值是方法名前加上`get``set`。
│   ├── 属性的可枚举性和遍历
│   │     └─ 可枚举性
│   │         └─ 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。`Object.getOwnPropertyDescriptor`方法可以获取该属性的描述对象。
│   │         └─ `toString``length`属性的`enumerable`都是`false`,因此`for...in`不会遍历到这两个继承自原型的属性。
│   │     └─ 属性的遍历
│   │         └─     `for...in`循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
│   │         └─     `Object.keys`返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
│   │         └─     `Object.getOwnPropertyNames`返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
│   │         └─     `Object.getOwnPropertySymbols`返回一个数组,包含对象自身的所有 Symbol 属性的键名。
│   │         └─     `Reflect.ownKeys`返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
│   ├── super 关键字
│   │     └─     我们知道,`this`关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字`super`,指向当前对象的原型对象。
│   ├── 对象的扩展运算符
│   │     └─ 解构赋值
│   │         └─ 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
│   │         └─ 解构赋值 对象`o3`复制了`o2`,但是只复制了`o2`自身的属性,没有复制它的原型对象`o1`的属性。
│   │     └─ 扩展运算符
│   │         └─ 对象的扩展运算符(`...`)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
│   │         └─ 扩展运算符的参数对象之中,如果有取值函数`get`,这个函数是会执行的。
│   ├── AggregateError 错误对象
│   │     └─
├── 对象的新增方法
│   ├── Object.is()
│   │     └─ ES5 比较两个值是否相等,只有两个运算符:相等运算符(`==`)和严格相等运算符(`===`)。它们都有缺点,前者会自动转换数据类型,后者的`NaN`不等于自身,以及`+0`等于`-0`JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
│   │     └─ ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。`Object.is`就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
│   ├── Object.assign()
│   │     └─ 基本用法
│   │         └─ `Object.assign()`方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
│   │         └─ 如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果`undefined``null`不在首参数,就不会报错。
│   │     └─ 注意点
│   │         └─ `Object.assign()`方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
│   │         └─ 对于这种嵌套的对象,一旦遇到同名属性,`Object.assign()`的处理方法是替换,而不是添加。
│   │         └─ `Object.assign()`可以用来处理数组,但是会把数组视为对象。
│   │         └─ `Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
│   │     └─ 常见用途
│   │         └─ (1)为对象添加属性
│   │         └─ (2)为对象添加方法
│   │         └─ (3)克隆对象
│   │         └─ (4)合并多个对象
│   │         └─ (5)为属性指定默认值
│   ├── Object.getOwnPropertyDescriptors()
│   │     └─ ES5`Object.getOwnPropertyDescriptor()`方法会返回某个对象属性的描述对象(descriptor)。ES2017 引入了`Object.getOwnPropertyDescriptors()`方法,返回指定对象所有自身属性(非继承属性)的描述对象。
│   │     └─ `source`对象的`foo`属性的值是一个赋值函数,`Object.assign`方法将这个属性拷贝给`target1`对象,结果该属性的值变成了`undefined`。这是因为`Object.assign`方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。这时,`Object.getOwnPropertyDescriptors()`方法配合`Object.defineProperties()`方法,就可以实现正确拷贝。
│   ├── `__proto__`属性,Object.setPrototypeOf(),Object.getPrototypeOf()
│   │     └─ `__proto__`属性
│   │         └─ `__proto__`属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。目前,所有浏览器(包括 IE11)都部署了这个属性。
│   │     └─ Object.setPrototypeOf()
│   │         └─ `Object.setPrototypeOf`方法的作用与`__proto__`相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
│   │     └─ Object.setPrototypeOf()
│   │         └─ 该方法与`Object.setPrototypeOf`方法配套,用于读取一个对象的原型对象。
│   ├── Object.keys(),Object.values(),Object.entries()
│   │     └─ Object.keys()
│   │         └─ `Object.keys`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
│   │     └─ Object.values()
│   │         └─ `Object.values`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
│   │     └─ Object.entries()
│   │         └─ `Object.entries()`方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
│   ├── Object.fromEntries()
│   │     └─ `Object.fromEntries()`方法是`Object.entries()`的逆操作,用于将一个键值对数组转为对象。
├── 运算符的扩展
│   ├── 指数运算符
│   │     └─ ES2016 新增了一个指数运算符(`**`2 ** 2 // 4 指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。
│   ├── 链判断运算符 `?.`
│   │     └─ const firstName = message?.body?.user?.firstName || 'default';
│   │     └─ myForm.checkValidity?.() === false 老式浏览器的表单对象可能没有`checkValidity()`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。
│   │     └─ `?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null``undefined`。如果是的,就不再往下运算,而是返回`undefined`。
│   │         └─ (1)短路机制
│   │               └─ 本质上,`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。
│   │         └─ (2)括号的影响
│   │               └─ 如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。
│   │         └─ (3)报错场合
│   │               └─ 构造函数 ; 链判断运算符的右侧有模板字符串 ; 链判断运算符的左侧是 super ; 链运算符用于赋值运算符左侧
│   │         └─ (4)右侧不得为十进制数值
│   │               └─ 为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
│   ├── Null 判断运算符 `??`
│   │     └─ const headerText = response.settings.headerText ?? 'Hello, world!';
│   │     └─ 读取对象属性的时候,如果某个属性的值是`null``undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。
│   │     └─ `??`本质上是逻辑运算,它与其他两个逻辑运算符`&&``||`有一个优先级问题  必须加入表明优先级的括号。
│   ├── 逻辑赋值运算符 `||=``&&=``??=`
│   │     └─ 这三个运算符`||=``&&=``??=`相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。
├── Symbol
│   ├── 概述
│   │     └─ ES6 引入了一种新的原始数据类型`Symbol`,表示独一无二的值。它属于 JavaScript 语言的数据类型之一,其他数据类型是:`undefined``null`、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。
│   │     └─ `Symbol` 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
│   │     └─ `Symbol` 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的`Symbol`函数的返回值是不相等的。
│   │     └─ `Symbol` 值也可以转为布尔值,但是不能转为数值。
│   ├── Symbol.prototype.description
│   │     └─ 创建 Symbol 的时候,可以添加一个描述。
│   ├── 作为属性名的 Symbol
│   │     └─ 由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
│   │     └─ Object.defineProperty(a, Symbol(), { value: 'Hello!' });
│   │     └─ Symbol 值作为对象属性名时,不能用点运算符 ; 同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
│   ├── 属性名的遍历
│   │     └─ Symbol 作为属性名,遍历对象的时候,该属性不会出现在`for...in``for...of`循环中,也不会被`Object.keys()``Object.getOwnPropertyNames()``JSON.stringify()`返回。
│   │     └─ 但是,它也不是私有属性,有一个`Object.getOwnPropertySymbols()`方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
│   │     └─
│   │     └─
│   ├── Symbol.for(),Symbol.keyFor()
│   │     └─ Symbol.for()
│   │         └─ 有时,我们希望重新使用同一个 Symbol 值,`Symbol.for()`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
│   │         └─ `Symbol.for()``Symbol()`这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的`key`是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30 次,每次都会返回同一个 Symbol 值,但是调用`Symbol("cat")`30 次,会返回 30 个不同的 Symbol 值。
│   │     └─ Symbol.keyFor()
│   │         └─ `Symbol.keyFor()`方法返回一个已登记的 Symbol 类型值的`key`
│   │         └─ `Symbol.for()`Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。
│   ├── 实例:模块的 Singleton 模式
│   │     └─ Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。
│   ├── 内置的 Symbol 值
│   │     └─ 除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
│   │     └─ Symbol.hasInstance
│   │         └─ 对象的`Symbol.hasInstance`属性,指向一个内部方法。当其他对象使用`instanceof`运算符,判断是否为该对象的实例时,会调用这个方法。比如,`foo instanceof Foo`在语言内部,实际调用的是`Foo[Symbol.hasInstance](foo)`。
│   │     └─ Symbol.isConcatSpreadable
│   │         └─ 对象的`Symbol.isConcatSpreadable`属性等于一个布尔值,表示该对象用于`Array.prototype.concat()`时,是否可以展开。
│   │         └─ 数组的默认行为是可以展开,`Symbol.isConcatSpreadable`默认等于`undefined`。该属性等于`true`时,也有展开的效果。
│   │         └─ 类似数组的对象正好相反,默认不展开。它的`Symbol.isConcatSpreadable`属性设为`true`,才可以展开。
│   │     └─ Symbol.species
│   │         └─ 对象的`Symbol.species`属性,指向一个构造函数。创建衍生对象时,会使用该属性。
│   │         └─ 子类`MyArray`继承了父类`Array``a``MyArray`的实例,`b``c``a`的衍生对象。你可能会认为,`b``c`都是调用数组方法生成的,所以应该是数组(`Array`的实例),但实际上它们也是`MyArray`的实例。
│   │         └─ 由于定义了`Symbol.species`属性,创建衍生对象时就会使用这个属性返回的函数,作为构造函数。这个例子也说明,定义`Symbol.species`属性要采用`get`取值器。
│   │         └─ `Symbol.species`的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。
│   │     └─ Symbol.match
│   │         └─ 对象的`Symbol.match`属性,指向一个函数。当执行`str.match(myObject)`时,如果该属性存在,会调用它,返回该方法的返回值。
│   │     └─ Symbol.replace
│   │         └─ 对象的`Symbol.replace`属性,指向一个方法,当该对象被`String.prototype.replace`方法调用时,会返回该方法的返回值。
│   │     └─ Symbol.search
│   │         └─ 对象的`Symbol.search`属性,指向一个方法,当该对象被`String.prototype.search`方法调用时,会返回该方法的返回值。
│   │     └─ Symbol.split
│   │         └─ 对象的`Symbol.split`属性,指向一个方法,当该对象被`String.prototype.split`方法调用时,会返回该方法的返回值。
│   │     └─ Symbol.iterator
│   │         └─ 对象的`Symbol.iterator`属性,指向该对象的默认遍历器方法。
│   │     └─ Symbol.toPrimitive
│   │         └─ 对象的`Symbol.toPrimitive`属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
│   │         └─ `Symbol.toPrimitive`被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
│   │         └─ - Number:该场合需要转成数值 - String:该场合需要转成字符串 - Default:该场合可以转成数值,也可以转成字符串
│   │     └─ Symbol.toStringTag
│   │         └─ 对象的`Symbol.toStringTag`属性,指向一个方法。在该对象上面调用`Object.prototype.toString`方法时,如果这个属性存在,它的返回值会出现在`toString`方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]``[object Array]``object`后面的那个字符串。
├── SetMap 数据结构
│   ├── Set
│   │     └─ 基本用法
│   │         └─ ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
│   │         └─ `Set`函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
│   │         └─ 向 Set 加入值的时候,不会发生类型转换,所以`5``"5"`是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(`===`),主要的区别是向 Set 加入值时认为`NaN`等于自身,而精确相等运算符认为`NaN`不等于自身。
│   │         └─ 向 Set 实例添加了两次`NaN`,但是只会加入一个。这表明,在 Set 内部,两个`NaN`是相等的。
│   │     └─ Set 实例的属性和方法
│   │         └─ Set 结构的实例有以下属性。
│   │             └─ - `Set.prototype.constructor`:构造函数,默认就是`Set`函数。
│   │             └─ - `Set.prototype.size`:返回`Set`实例的成员总数。
│   │         └─ Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
│   │             └─ - `Set.prototype.add(value)`:添加某个值,返回 Set 结构本身。
│   │             └─ - `Set.prototype.delete(value)`:删除某个值,返回一个布尔值,表示删除是否成功。
│   │             └─ - `Set.prototype.has(value)`:返回一个布尔值,表示该值是否为`Set`的成员。
│   │             └─ - `Set.prototype.clear()`:清除所有成员,没有返回值。
│   │         └─ `Array.from`方法可以将 Set 结构转为数组。
│   │     └─ 遍历操作
│   │         └─ Set 结构的实例有四个遍历方法,可以用于遍历成员。
│   │             └─ - `Set.prototype.keys()`:返回键名的遍历器
│   │             └─ - `Set.prototype.values()`:返回键值的遍历器
│   │             └─ - `Set.prototype.entries()`:返回键值对的遍历器
│   │             └─ - `Set.prototype.forEach()`:使用回调函数遍历每个成员
│   │                 └─ Set 结构的实例与数组一样,也拥有`forEach`方法,用于对每个成员执行某种操作,没有返回值。
│   │                 └─ `forEach`方法的参数就是一个处理函数。该函数的参数与数组的`forEach`一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。
│   │             └─ 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以`keys`方法和`values`方法的行为完全一致。
│   │             └─ 扩展运算符(`...`)内部使用`for...of`循环,所以也可以用于 Set 结构。 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。 [...new Set(arr)];
│   │             └─ 数组的`map``filter`方法也可以间接用于 Set
│   │             └─ 并集 new Set([...a, ...b]) 交集new Set([...a].filter(x => b.has(x))  (a 相对于 b 的)差集 new Set([...a].filter(x => !b.has(x)))
│   │             └─ 如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用`Array.from`方法。
│   ├── WeakSet
│   │     └─ 含义
│   │         └─ WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
│   │         └─ WeakSet 的成员只能是对象,而不能是其他类型的值。
│   │         └─ 其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
│   │         └─ WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
│   │     └─ 语法
│   │         └─ WeakSet 是一个构造函数,可以使用`new`命令,创建 WeakSet 数据结构。
│   │         └─ 作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。
│   │         └─ WeakSet 结构有以下三个方法。
│   │             └─ - WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
│   │             └─ - WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
│   │             └─ - WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
│   │         └─ WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
│   ├── Map
│   │     └─ 含义和基本用法
│   │         └─ JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
│   │         └─ 为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,MapObject 更合适。
│   │         └─ 如果对同一个键多次赋值,后面的值将覆盖前面的值。
│   │         └─ Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
│   │         └─ 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如`0``-0`就是一个键,布尔值`true`和字符串`true`则是两个不同的键。另外,`undefined``null`也是两个不同的键。虽然`NaN`不严格相等于自身,但 Map 将其视为同一个键。
│   │     └─ 实例的属性和操作方法
│   │         └─ (1)size 属性
│   │             └─ `size`属性返回 Map 结构的成员总数。
│   │         └─ (2Map.prototype.set(key, value)
│   │             └─ `set`方法设置键名`key`对应的键值为`value`,然后返回整个 Map 结构。如果`key`已经有值,则键值会被更新,否则就新生成该键。`set`方法返回的是当前的`Map`对象,因此可以采用链式写法。
│   │         └─ (3Map.prototype.get(key)
│   │             └─ `get`方法读取`key`对应的键值,如果找不到`key`,返回`undefined`。
│   │         └─ (4Map.prototype.has(key)
│   │             └─ `has`方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
│   │         └─ (5Map.prototype.delete(key)
│   │             └─ `delete`方法删除某个键,返回`true`。如果删除失败,返回`false`。
│   │         └─ (6Map.prototype.clear()
│   │             └─ `clear`方法清除所有成员,没有返回值。
│   │     └─ 遍历方法
│   │         └─ - `Map.prototype.keys()`:返回键名的遍历器。
│   │         └─ - `Map.prototype.values()`:返回键值的遍历器。
│   │         └─ - `Map.prototype.entries()`:返回所有成员的遍历器。
│   │         └─ - `Map.prototype.forEach()`:遍历 Map 的所有成员。
│   │         └─ Map 结构转为数组结构,比较快速的方法是使用扩展运算符(`...`)。
│   │     └─ 与其他数据结构的互相转换
│   │         └─ (1Map 转为数组
│   │             └─ 前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(`...`)。
│   │         └─ (2)数组 转为 Map
│   │             └─ 将数组传入 Map 构造函数,就可以转为 Map。
│   │         └─ (3Map 转为对象
│   │             └─ 如果所有 Map 的键都是字符串,它可以无损地转为对象。
│   │         └─ (4)对象转为 Map
│   │             └─ 对象转为 Map 可以通过`Object.entries()`。
│   │         └─ (5Map 转为 JSON
│   │             └─ Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
│   │             └─ 另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
│   │         └─ (6JSON 转为 Map
│   │             └─ JSON 转为 Map,正常情况下,所有键名都是字符串。
│   ├── WeakMap
│   │     └─ 含义
│   │         └─ `WeakMap`结构与`Map`结构类似,也是用于生成键值对的集合。
│   │         └─ `WeakMap``Map`的区别有两点。
│   │             └─ 首先,`WeakMap`只接受对象作为键名(`null`除外),不接受其他类型的值作为键名。
│   │             └─ 其次,`WeakMap`的键名所指向的对象,不计入垃圾回收机制。
│   │         └─ `e1``e2`是两个对象,我们通过`arr`数组对这两个对象添加一些文字说明。这就形成了`arr``e1``e2`的引用。一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放`e1``e2`占用的内存。WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用`WeakMap`结构。当该 DOM 元素被清除,其所对应的`WeakMap`记录就会自动被移除。
│   │     └─ WeakMap 的语法
│   │         └─ WeakMapMapAPI 上的区别主要是两个,一是没有遍历操作(即没有`keys()``values()``entries()`方法),也没有`size`属性。因为没有办法列出所有键名,某个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。这一刻可以取到键名,下一刻垃圾回收机制突然运行了,这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。二是无法清空,即不支持`clear`方法。因此,`WeakMap`只有四个方法可用:`get()``set()``has()``delete()`。
│   │     └─ WeakMap 的示例
│   │         └─ WeakMap 的例子很难演示,因为无法观察它里面的引用会自动消失。此时,其他引用都解除了,已经没有引用指向 WeakMap 的键名了,导致无法证实那个键名是不是存在。
│   │     └─ WeakMap 的用途
│   │         └─ WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。
│   ├── WeakRef
│   │     └─ WeakSetWeakMap 是基于弱引用的数据结构 更进一步,提供了 WeakRef 对象,用于直接创建对象的弱引用。
│   │     └─ `target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的实例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引用不会妨碍原始对象`target`被垃圾回收机制清除。
│   │     └─ WeakRef 实例对象有一个`deref()`方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回`undefined`。
│   ├── FinalizationRegistry
│   │     └─ 引入了清理器注册表功能 FinalizationRegistry,用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数。
│   │     └─ `FinalizationRegistry()`是系统提供的构造函数,返回一个清理器注册表实例,里面登记了所要执行的回调函数。回调函数作为`FinalizationRegistry()`的参数传入,它本身有一个参数`heldValue`。
│   │     └─ registry.register(theObject, "some value"); `theObject`就是所要观察的目标对象,一旦该对象被垃圾回收机制清除,注册表就会在清除完成后,调用早前注册的回调函数,并将`some value`作为参数(前面的`heldValue`)传入回调函数。
├── Proxy
│   ├── 概述
│   │     └─ Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
│   │     └─ Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
│   │     └─ 作为构造函数,`Proxy`接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有`Proxy`的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
│   │     └─ 注意,要使得`Proxy`起作用,必须针对`Proxy`实例(上例是`proxy`对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
│   │     └─ 如果`handler`没有设置任何拦截,那就等同于直接通向原对象。`handler`是一个空对象,没有任何拦截效果,访问`proxy`就等同于访问`target`。
│   │     └─ Proxy 实例也可以作为其他对象的原型对象。
│   │     └─ 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)`。
│   ├── Proxy 实例的方法
│   │     └─ get()
│   │         └─ `get`方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
│   │         └─ 如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回`undefined``get`方法可以继承。
│   │         └─ 利用 Proxy,可以将读取属性的操作(`get`),转变为执行某个函数,从而实现属性的链式操作。
│   │     └─ apply()
│   │         └─ `apply`方法拦截函数的调用、`call``apply`操作。 另外,直接调用`Reflect.apply`方法,也会被拦截。
│   │         └─ `apply`方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(`this`)和目标对象的参数数组。
│   │     └─ has()
│   │         └─ `has()` 方法用来拦截 `HasProperty`操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是`in`运算符。
│   │         └─ `has()`方法可以接受两个参数,分别是目标对象、需查询的属性名。
│   │         └─ Object.preventExtensions() 方法让一个对象变的不可扩展,也就是永远不能再添加新的属性
│   │         └─ `has()`方法拦截的是`HasProperty`操作,而不是`HasOwnProperty`操作,即`has()`方法不判断一个属性是对象自身的属性,还是继承的属性。虽然`for...in`循环也用到了`in`运算符,但是`has()`拦截对`for...in`循环不生效。
│   │     └─ construct()
│   │         └─ `construct()`方法用于拦截`new`命令,下面是拦截对象的写法。
│   │         └─ `construct()`方法可以接受三个参数。 - `target`:目标对象。 - `args`:构造函数的参数数组。 
│   │         └─ `construct()`方法返回的必须是一个对象,否则会报错。另外,由于`construct()`拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。
│   │         └─ `construct()`方法中的`this`指向的是`handler`,而不是实例对象。
│   │     └─ deleteProperty()
│   │         └─ `deleteProperty`方法用于拦截`delete`操作,如果这个方法抛出错误或者返回`false`,当前属性就无法被`delete`命令删除。
│   │         └─ 注意,目标对象自身的不可配置(configurable)的属性,不能被`deleteProperty`方法删除,否则报错。
│   │     └─ defineProperty()
│   │         └─ `defineProperty()`方法拦截了`Object.defineProperty()`操作。
│   │         └─ `defineProperty()`方法内部没有任何操作,只返回`false`,导致添加新属性总是无效。注意,这里的`false`只是用来提示操作失败,本身并不能阻止添加新属性。
│   │         └─ 如果目标对象不可扩展(non-extensible),则`defineProperty()`不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则`defineProperty()`方法不得改变这两个设置。
│   │     └─ getOwnPropertyDescriptor()
│   │         └─ `getOwnPropertyDescriptor()`方法拦截`Object.getOwnPropertyDescriptor()`,返回一个属性描述对象或者`undefined`。
│   │     └─ getPrototypeOf()
│   │         └─ `getPrototypeOf()`方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
│   │             └─ - `Object.prototype.__proto__`
│   │             └─ - `Object.prototype.isPrototypeOf()`
│   │             └─ - `Object.getPrototypeOf()`
│   │             └─ - `Reflect.getPrototypeOf()`
│   │             └─ - `instanceof`
│   │         └─ `getPrototypeOf()`方法的返回值必须是对象或者`null`,否则报错。另外,如果目标对象不可扩展(non-extensible), `getPrototypeOf()`方法必须返回目标对象的原型对象。
│   │     └─ isExtensible()
│   │         └─ `isExtensible()`方法拦截`Object.isExtensible()`操作。
│   │         └─ 该方法只能返回布尔值,否则返回值会被自动转为布尔值。这个方法有一个强限制,它的返回值必须与目标对象的`isExtensible`属性保持一致,否则就会抛出错误。
│   │     └─ ownKeys()
│   │         └─ `ownKeys()`方法用来拦截对象自身属性的读取操作。
│   │             └─ - `Object.getOwnPropertyNames()`
│   │             └─ - `Object.getOwnPropertySymbols()`
│   │             └─ - `Object.keys()`
│   │             └─ - `for...in`循环
│   │         └─ 使用`Object.keys()`方法时,有三类属性会被`ownKeys()`方法自动过滤,不会返回。
│   │             └─ 目标对象上不存在的属性
│   │             └─ 属性名为 Symbol 值
│   │             └─ 不可遍历(`enumerable`)的属性
│   │             └─ 
│   │         └─ `ownKeys()`方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
│   │     └─ preventExtensions()
│   │         └─ `preventExtensions()`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值,否则会被自动转为布尔值。
│   │         └─ 这个方法有一个限制,只有目标对象不可扩展时(即`Object.isExtensible(proxy)``false`),`proxy.preventExtensions`才能返回`true`,否则会报错。
│   │     └─ setPrototypeOf()
│   │         └─ `setPrototypeOf()`方法主要用来拦截`Object.setPrototypeOf()`方法。
│   │         └─ 如果目标对象不可扩展(non-extensible),`setPrototypeOf()`方法不得改变目标对象的原型。
│   ├── Proxy.revocable()
│   │     └─ `Proxy.revocable()`方法返回一个可取消的 Proxy 实例。
│   │     └─ `Proxy.revocable()`方法返回一个对象,该对象的`proxy`属性是`Proxy`实例,`revoke`属性是一个函数,可以取消`Proxy`实例。
│   │     └─ `Proxy.revocable()`的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
│   │         └─ 
│   │         └─ 
│   ├── this 问题
│   │     └─ 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的`this`关键字会指向 Proxy 代理。
│   │     └─ const target = new Date(); const handler = {}; const proxy = new Proxy(target, handler); proxy.getDate();
│   │         └─ `getDate()`方法只能在`Date`对象实例上面拿到,如果`this`不是`Date`对象实例就会报错。这时,`this`绑定原始对象,就可以解决这个问题。
│   │         └─ Proxy 拦截函数内部的`this`,指向的是`handler` 对象。this === handler
│   │         └─ `get()``set()`拦截函数内部的`this`,指向的都是`handler`对象。
│   ├── Web 服务的客户端
│   │     └─ Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。
├── Reflect
│   ├── 概述
│   │     └─ `Reflect`对象与`Proxy`对象一样,也是 ES6 为了操作对象而提供的新 API`Reflect`对象的设计目的有这样几个。
│   │     └─ 
│   ├── 设计目的
│   │     └─ (1) 将`Object`对象的一些明显属于语言内部的方法(比如`Object.defineProperty`),放到`Reflect`对象上。现阶段,某些方法同时在`Object``Reflect`对象上部署,未来的新方法将只部署在`Reflect`对象上。也就是说,从`Reflect`对象上可以拿到语言内部的方法。
│   │     └─ (2) 修改某些`Object`方法的返回结果,让其变得更合理。比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回`false`。
│   │     └─ (3) 让`Object`操作都变成函数行为。某些`Object`操作是命令式,比如`name in obj``delete obj[name]`,而`Reflect.has(obj, name)``Reflect.deleteProperty(obj, name)`让它们变成了函数行为。
│   │     └─ (4`Reflect`对象的方法与`Proxy`对象的方法一一对应,只要是`Proxy`对象的方法,就能在`Reflect`对象上找到对应的方法。这就让`Proxy`对象可以方便地调用对应的`Reflect`方法,完成默认行为,作为修改行为的基础。也就是说,不管`Proxy`怎么修改默认行为,你总可以在`Reflect`上获取默认行为。
│   │     └─ 
│   ├── 静态方法
│   │     └─ - 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)
│   │     └─ 上面这些方法的作用,大部分与`Object`对象的同名方法的作用都是相同的,而且它与`Proxy`对象的方法是一一对应的。下面是对它们的解释。
│   ├── 静态方法
│   │     └─ Reflect.get(target, name, receiver)
│   │         └─ `Reflect.get`方法查找并返回`target`对象的`name`属性,如果没有该属性,则返回`undefined`。
│   │         └─ 如果`name`属性部署了读取函数(getter),则读取函数的`this`绑定`receiver`。
│   │     └─ Reflect.set(target, name, value, receive
│   │         └─ `Reflect.set`方法设置`target`对象的`name`属性等于`value`。
│   │         └─ 如果`name`属性设置了赋值函数,则赋值函数的`this`绑定`receiver`。
│   │         └─ 注意,如果 `Proxy`对象和 `Reflect`对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且传入了`receiver`,那么`Reflect.set`会触发`Proxy.defineProperty`拦截。
│   │     └─ Reflect.has(obj, name)
│   │         └─ `Reflect.has`方法对应`name in obj`里面的`in`运算符。
│   │     └─ Reflect.deleteProperty(obj, name)
│   │         └─ 用于删除对象的属性。
│   │     └─ Reflect.construct(target, args)
│   │         └─ `Reflect.construct`方法等同于`new target(...args)`,这提供了一种不使用`new`,来调用构造函数的方法。
│   │     └─ Reflect.getPrototypeOf(obj)
│   │         └─ `Reflect.getPrototypeOf`方法用于读取对象的`__proto__`属性,对应`Object.getPrototypeOf(obj)`。
│   │     └─ Reflect.setPrototypeOf(obj, newProto)
│   │         └─ `Reflect.setPrototypeOf`方法用于设置目标对象的原型(prototype),对应`Object.setPrototypeOf(obj, newProto)`方法。它返回一个布尔值,表示是否设置成功。
│   │     └─ Reflect.apply(func, thisArg, args)
│   │         └─ 用于绑定 `this`对象后执行给定函数。
│   │         └─ ,但是如果函数定义了自己的`apply`方法 Reflect.apply(Math.min, Math, ages);
│   │     └─ Reflect.defineProperty(target, propertyKey, attributes)
│   │         └─ 用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用`Reflect.defineProperty`代替它。
│   │         └─ `Proxy.defineProperty`对属性赋值设置了拦截,然后使用`Reflect.defineProperty`完成了赋值。
│   │     └─ Reflect.getOwnPropertyDescriptor(target, propertyKey)
│   │         └─ 用于得到指定属性的描述对象,将来会替代掉后者。
│   │     └─ Reflect.isExtensible (target)
│   │         └─ 用来表示当前对象是否可扩展。返回一个布尔值,
│   │     └─ Reflect.preventExtensions(target)
│   │         └─ 用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
│   │     └─ Reflect.ownKeys (target)
│   │         └─ 用于返回对象的所有属性 基本等同于`Object.getOwnPropertyNames``Object.getOwnPropertySymbols`之和。
│   ├── 使用 Proxy 实现观察者模式
│   │     └─ 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
├── Promise 对象
│   ├── Promise 的含义
│   │     └─ 所谓`Promise`,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
│   │     └─ `Promise`对象有以下两个特点。
│   │         └─ (1)对象的状态不受外界影响。
│   │         └─ (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
│   │     └─ 
│   ├── 基本用法
│   │     └─ `Promise`对象是一个构造函数,用来生成`Promise`实例。
│   │     └─ `then`方法可以接受两个回调函数作为参数。第一个回调函数是`Promise`对象的状态变为`resolved`时调用,第二个回调函数是`Promise`对象的状态变为`rejected`时调用。
│   ├── Promise.prototype.then()
│   │     └─ Promise 实例具有`then`方法,也就是说,`then`方法是定义在原型对象`Promise.prototype`上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,`then`方法的第一个参数是`resolved`状态的回调函数,第二个参数是`rejected`状态的回调函数,它们都是可选的。
│   ├── Promise.prototype.catch()
│   │     └─ `Promise.prototype.catch()`方法是`.then(null, rejection)``.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数。
│   │     └─ Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
│   ├── Promise.prototype.finally()
│   │     └─ `finally()`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
│   │     └─ `finally`方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是`fulfilled`还是`rejected`。这表明,`finally`方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。`finally`本质上是`then`方法的特例。
│   ├── Promise.all()
│   │     └─ `Promise.all()`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
│   │     └─ `Promise.all()`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
│   │     └─ 当每个 Promise 实例 rejected 但是自己有 catch 方法, 这样 `Promise.all()`方法参数里面实例就会是`resolved`状态, 那么就会走到 then 而不是 catch
│   ├── Promise.race()
│   │     └─ `Promise.race()`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
│   │     └─ const p = Promise.race([p1, p2, p3]); 只要`p1``p2``p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。
│   ├── Promise.allSettled()
│   │     └─ `Promise.allSettled()`方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
│   │     └─ `results`的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果。 {status: 'fulfilled', value: value}
│   │     └─ 成员对象的`status`属性的值只可能是字符串`fulfilled`或字符串`rejected`,用来区分异步操作是成功还是失败。如果是成功(`fulfilled`),对象会有`value`属性,如果是失败(`rejected`),会有`reason`属性,对应两种状态时前面异步操作的返回值。
│   ├── Promise.any()
│   │     └─ 该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
│   │     └─ 只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。
│   │     └─ `Promise.any()`不会因为某个 Promise 变成`rejected`状态而结束,必须等到所有参数 Promise 变成`rejected`状态才会结束。
│   │     └─ 其中只要有一个变成`fulfilled``Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。
│   ├── Promise.resolve()
│   │     └─ 有时需要将现有对象转为 Promise 对象,`Promise.resolve()`方法就起到这个作用。
│   │     └─ `Promise.resolve()`方法的参数分成四种情况。
│   │         └─ **(1)参数是一个 Promise 实例** 如果参数是 Promise 实例,那么`Promise.resolve`将不做任何修改、原封不动地返回这个实例。
│   │         └─ **(2)参数是一个 `thenable`对象** `thenable`对象指的是具有`then`方法的对象 `Promise.resolve()`方法会将这个对象转为 Promise 对象,然后就立即执行`thenable`对象的`then()`方法。
│   │         └─ **(3)参数不是具有 `then()`方法的对象,或根本就不是对象** 如果参数是一个原始值,或者是一个不具有`then()`方法的对象,则`Promise.resolve()`方法返回一个新的 Promise 对象,状态为`resolved`。
│   │         └─ **(4)不带有任何参数** `Promise.resolve()`方法允许调用时不带参数,直接返回一个`resolved`状态的 Promise 对象。
│   ├── Promise.reject()
│   │     └─ `Promise.reject(reason)`方法也会返回一个新的 Promise 实例,该实例的状态为`rejected`。
│   ├── 应用
│   │     └─ 加载图片
│   │         └─ 我们可以将图片的加载写成一个`Promise`,一旦加载完成,`Promise`的状态就发生变化。
│   │     └─ Generator 函数与 Promise 的结合
│   │         └─ 使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个`Promise`对象。
│   ├── Promise.try()
│   │         └─ try { database.users.get({id: userId}) .then(...) .catch(...)} catch (e) {// ...}
│   │         └─ Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
│   │         └─ `Promise.try`就是模拟`try`代码块,就像`promise.catch`模拟的是`catch`代码块。
├── Iteratorfor...of 循环
│   ├── Iterator(遍历器)的概念
│   │     └─ 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
│   │     └─ Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令`for...of`循环,Iterator 接口主要供`for...of`消费。
│   │     └─ Iterator 的遍历过程
│   │         └─ (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
│   │         └─ (2)第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员。

│   │         └─ (3)第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员
│   │         └─ (4)不断调用指针对象的`next`方法,直到它指向数据结构的结束位置。
│   ├── 默认 Iterator 接口
│   │     └─ 
│   │     └─ ES6 规定,默认的 Iterator 接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(iterable)。
│   │     └─ 普通对象部署数组的`Symbol.iterator`方法,并无效果。
│   │     └─ 如果`Symbol.iterator`方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
│   │     └─ 有了遍历器接口,数据结构就可以用`for...of`循环遍历(详见下文),也可以使用`while`循环遍历。
│   ├── 默认调用 Iterator 接口的场合
│   │     └─ (1)解构赋值 对数组和 Set 结构进行解构赋值时,会默认调用`Symbol.iterator`方法。
│   │     └─ (2)扩展运算符 扩展运算符(...)也会调用默认的 Iterator 接口。
│   │     └─ (3yield* `yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
│   │     └─ (4)其他场合 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
│   │         └─ - for...of
│   │         └─ - Array.from()
│   │         └─ - Map(), Set(), WeakMap(), WeakSet()(比如`new Map([['a',1],['b',2]])`)
│   │         └─ - Promise.all()
│   │         └─ - Promise.race()
│   ├── 字符串的 Iterator 接口
│   │     └─ 字符串是一个类似数组的对象,也原生具有 Iterator 接口。
│   │     └─ 调用`Symbol.iterator`方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历。可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。
│   ├── Iterator 接口与 Generator 函数
│   │     └─ [Symbol.iterator]: function* () {} 
│   │     └─ * [Symbol.iterator]() {}
│   │     └─ `Symbol.iterator()`方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。
│   ├── 遍历器对象的 return(),throw()
│   │     └─ 遍历器对象除了具有`next()`方法,还可以具有`return()`方法和`throw()`方法。如果你自己写遍历器对象生成函数,那么`next()`方法是必须部署的,`return()`方法和`throw()`方法是否部署是可选的。
│   │     └─ `return()`方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有`break`语句),就会调用`return()`方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署`return()`方法。
│   │     └─ `return()`方法必须返回一个对象,这是 Generator 语法决定的。
│   ├── for...of 循环
│   │     └─ 一个数据结构只要部署了`Symbol.iterator`属性,就被视为具有 iterator 接口,就可以用`for...of`循环遍历它的成员。也就是说,`for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。
│   │     └─ 数组
│   │         └─ 数组原生具备`iterator`接口(即默认部署了`Symbol.iterator`属性),`for...of`循环本质上就是调用这个接口产生的遍历器,
│   │         └─ `for...of`循环可以代替数组实例的`forEach`方法。
│   │         └─ `for...in`循环读取键名,`for...of`循环读取键值。
│   │         └─ `for...of`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性
│   │     └─ SetMap 结构
│   │         └─ SetMap 结构也原生具有 Iterator 接口,可以直接使用`for...of`循环。
│   │         └─ 值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
│   │     └─ 计算生成的数据结构
│   │         └─ 有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、SetMap 都部署了以下三个方法,调用后都返回遍历器对象。
│   │         └─ - `entries()` 返回一个遍历器对象,用来遍历`[键名, 键值]`组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用`entries`方法。
│   │         └─ - `keys()` 返回一个遍历器对象,用来遍历所有的键名。
│   │         └─ - `values()` 返回一个遍历器对象,用来遍历所有的键
│   │     └─ 类似数组的对象
│   │         └─ 类似数组的对象包括好几类。下面是`for...of`循环用于字符串、DOM NodeList 对象、`arguments`对象的例子。
│   │         └─ 对于字符串来说,`for...of`循环还有一个特点,就是会正确识别 32UTF-16 字符。
│   │         └─ 并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用`Array.from`方法将其转为数组
│   │     └─ 对象
│   │         └─ 对于普通的对象,`for...of`结构不能直接使用,会报错,对于普通的对象,`for...in`循环可以遍历键名,`for...of`循环会报错。
│   │         └─ 一种解决方法是,使用`Object.keys`方法将对象的键名生成一个数组,然后遍历这个数组。 另一个方法是使用 Generator 函数将对象重新包装一下。
│   │     └─ 与其他遍历语法的比较
│   │         └─ - 有着同`for...in`一样的简洁语法,但是没有`for...in`那些缺点。
│   │         └─ - 不同于`forEach`方法,它可以与`break``continue``return`配合使用。
│   │         └─ - 提供了遍历所有数据结构的统一操作接口。
├── Generator
│   ├── 简介
│   │     └─ 基本概念
│   │         └─ Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
│   │         └─ 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。
│   │         └─ Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是必须调用遍历器对象的 `next`方法,使得指针移向下一个状态。
│   │         └─ function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· } 写法均可
│   │     └─ yield 表达式
│   │         └─ `yield`表达式与`return`语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能。
│   │         └─ 正常函数只能返回一个值,因为只能执行一次`return`Generator 函数可以返回一系列的值,因为可以有任意多个`yield`。
│   │         └─ 一个函数里面,只能执行一次(或者说一个)`return`语句,但是可以执行多次(或者说多个)`yield`表达式。
│   ├── for...of 循环
│   │     └─ `for...of`循环可以自动遍历 Generator 函数运行时生成的`Iterator`对象,且此时不再需要调用`next`方法。
│   │     └─ 除了`for...of`循环以外,扩展运算符(`...`)、解构赋值和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参
│   ├── Generator.prototype.throw()
│   │     └─ Generator 函数返回的遍历器对象,都有一个`throw`方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
│   │     └─ 遍历器对象`i`连续抛出两个错误。第一个错误被 Generator 函数体内的`catch`语句捕获。`i`第二次抛出错误,由于 Generator 函数内部的`catch`语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的`catch`语句捕获。
│   │     └─ `throw`方法可以接受一个参数,该参数会被`catch`语句接收,建议抛出`Error`对象的实例。
│   │     └─ `throw`命令与`g.throw`方法是无关的,两者互不影响。 不要混淆遍历器对象的`throw`方法和全局的`throw`命令
│   │     └─ 如果 Generator 函数内部没有部署`try...catch`代码块,那么`throw`方法抛出的错误,将被外部`try...catch`代码块捕获。
│   │     └─ `throw`方法抛出的错误要被内部捕获,前提是必须至少执行过一次`next`方法。
│   │     └─ `throw`方法被捕获以后,自动执行了一次`next`方法
│   ├── Generator.prototype.return()
│   │     └─ Generator 函数返回的遍历器对象还有一个`return()`方法,可以返回给定的值,并且终结遍历 Generator 函数。
│   │     └─ Generator 函数返回的遍历器对象`g`调用`return()`方法后,返回值的`value`属性就是`return()`方法的参数`foo`. 并且,Generator 函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next()`方法,`done`属性总是返回`true`。如果`return()`方法调用时,不提供参数,则返回值的`value`属性为`undefined`。
│   │     └─ Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return()`方法会导致立刻进入`finally`代码块,执行完以后,整个函数才会结束。
│   ├── next()、throw()、return() 的共同点
│   │     └─ 它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换`yield`表达式。
│   │     └─ `next()`是将`yield`表达式替换成一个值。
│   │     └─ `throw()`是将`yield`表达式替换成一个`throw`语句。
│   │     └─ `return()`是将`yield`表达式替换成一个`return`语句。
│   ├── yield* 表达式
│   │     └─ ES6 提供了`yield*`表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。
│   │     └─ 如果`yield`表达式后面跟的是一个遍历器对象,需要在`yield`表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为`yield*`表达式。
│   │     └─ 如果`yield*`后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。`yield`命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
│   ├── 作为对象属性的 Generator 函数
│   │     └─ 如果一个对象的属性是 Generator 函数,可以简写成下面的形式。let obj = { * myGeneratorMethod() { ··· } };
│   │     └─ 等同于 let obj = { myGeneratorMethod: function* () { // ··· };
│   ├── Generator 函数的`this`
│   │     └─ Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的`prototype`对象上的方法。
│   │     └─ Generator 函数也不能跟`new`命令一起用,会报错。
│   │     └─ 首先,生成一个空对象,使用`call`方法绑定 Generator 函数内部的`this`。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。
│   ├── 含义
│   │     └─ Generator 与状态机
│   │     └─ Generator 与协程
│   │         └─ 协程(coroutine)是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”。协程既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
│   │         └─ 从实现上看,在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。
│   │         └─ Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用`yield`表达式交换控制权。
│   │     └─ Generator 与上下文
│   │         └─ 堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。
│   │         └─ Generator 函数,它执行产生的上下文环境,一旦遇到`yield`命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行`next`命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
│   │         └─ 
│   ├── 应用
│   │     └─ Generator 可以暂停函数执行,返回任意表达式的值
│   │     └─ (1)异步操作的同步化表达
│   │         └─ `yield`表达式,本身是没有值的,总是等于`undefined`。
│   │     └─ (2)控制流管理
│   │     └─ (3)部署 Iterator 接口
│   │         └─ 利用 Generator 函数,可以在任意对象上部署 Iterator 接口。
├── Generator 函数的异步应用
│   ├── 传统方法
│   │     └─ 回调函数 事件监听 发布/订阅 Promise
│   ├── 基本概念
│   │     └─ 异步
│   │         └─ 所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
│   │     └─ 回调函数
│   │         └─ JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。回调函数的英语名字`callback`,直译过来就是"重新调用"。
│   │         └─ 为什么 Node 约定,回调函数的第一个参数,必须是错误对象`err` 原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。
│   │     └─ Promise
│   │         └─ Promise 的写法只是回调函数的改进,使用`then`方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
│   │         └─ Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆`then`,原来的语义变得很不清楚。
│   ├── Generator 函数
│   │     └─ 协程
│   │         └─ - 第一步,协程`A`开始执行。
│   │         └─ - 第二步,协程`A`执行到一半,进入暂停,执行权转移到协程`B`。
│   │         └─ - 第三步,(一段时间后)协程`B`交还执行权。
│   │         └─ - 第四步,协程`A`恢复执行。
│   │         └─ 协程的奥妙就在其中的 `yield`命令。它表示执行到此处,执行权将交给其他协程。也就是说,`yield`命令是异步两个阶段的分界线。
│   │     └─ 协程的 Generator 函数实现
│   │         └─ Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
│   │         └─ 整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用`yield`语句注明
│   │         └─ `next`方法的作用是分阶段执行`Generator`函数。每次调用`next`方法,会返回一个对象,表示当前阶段的信息(`value`属性和`done`属性)。`value`属性是`yield`语句后面表达式的值,表示当前阶段的值;`done`属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
│   │     └─ Generator 函数的数据交换和错误处理
│   │         └─ Generator 函数还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
│   │         └─ `next`返回值的 value 属性,是 Generator 函数向外输出数据;`next`方法还可以接受参数,向 Generator 函数体内输入数据。
│   │         └─ 
│   │     └─ 异步任务的封装
│   ├── Thunk 函数
│   │     └─ Thunk 函数是自动执行 Generator 函数的一种方法。
│   │     └─ 参数的求值策略
│   │         └─ 参数的求值策略
│   │         └─ 
│   │     └─ Thunk 函数的含义
│   │         └─ 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
│   │     └─ JavaScript 语言的 Thunk 函数
│   │         └─ JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。
│   │         └─ 任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。
│   │     └─ Thunkify 模块
│   │     └─ Generator 函数的流程管理
│   │     └─ Thunk 函数的自动流程管理
│   │         └─ Thunk 函数真正的威力,在于可以自动执行 Generator 函数。
│   │         └─ Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。
│   ├── co 模块
│   │     └─ 基本用法
│   │         └─ co 模块可以让你不用编写 Generator 函数的执行器。
│   │         └─ Generator 函数只要传入`co`函数,就会自动执行。`co`函数返回一个`Promise`对象,因此可以用`then`方法添加回调函数。
│   │     └─ co 模块的原理
│   │         └─ 自动执行 Generator 函数,两种方法可以做到这一点。
│   │         └─ co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的`yield`命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co,详见后文的例子。