共计 19543 个字符,预计需要花费 49 分钟才能阅读完成。
首先先相熟一下 Symbol 类型的定义及其作用:
- 能够用作对象属性键的非字符串值的汇合。
- 每个 Symbol 值都是惟一且不可变的。
- 每个 Symbol 值都与一个 [[Description]] 的值关联,该值要么是 undefined,要么是一个字符串。
内置的 Symbol 值次要是用于 ECMAScript 标准算法扩大的。本文次要是通过对标准的解读来理解 Symbol 内置的值是如何应用及其标准定义的。
咱们先浏览一下标准里的 Symbol 内置的值:
标准名称 | Description | 值及其作用 |
---|---|---|
@@asyncIterator | “Symbol.asyncIterator” | 一个返回异步迭代器的办法,次要用于 for await |
@@hasInstance | “Symbol.hasInstance” | 用于确认对象是否为该构造函数实例的办法,次要用于 instanceof |
@@isConcatSpreadable | “Symbol.isConcatSpreadable” | 一个 Boolean 值,标识是否能够通过 Array.prototype.concat 进行扁平化解决 |
@@iterator | “Symbol.iterator” | 一个返回异步迭代器的办法,次要用于 for of |
@@match | “Symbol.match” | 用于 String.prototype.match 调用 |
@@replace | “Symbol.replace” | 用于 String.prototype.replace 调用 |
@@search | “Symbol.search” | 用于 String.prototype.search 调用 |
@@species | “Symbol.species” | 一个用来返回创立派生对象的构造函数的办法 |
@@split | “Symbol.split” | 用于 String.prototype.split 调用 |
@@toPrimitive | “Symbol.toPrimitive” | 用于 ToPrimitive 形象办法 |
@@toStringTag | “Symbol.toStringTag” | 用于形容一个对象的字符串,次要用于 Object.prototype.toString 调用 |
@@unscopables | “Symbol.unscopables” | 用于 with 环境绑定中排除的属性名称 |
下面有些形容比拟形象,不要急,咱们将一一来认真理解其标准定义和作用
Symbol.hasInstance(@@hasInstance)
作用
和下面形容的一样,用于确认对象是否为该构造函数实例的办法,次要用于 instanceof, 当调用 instanceof 时,外部办法会调用对象上的 Symbol.hasInstance 办法。
咱们来看一个例子
class MyArray {static [Symbol.hasInstance](val){return val instanceof Array;}
}
[1,2,3] instanceof MyArray; // true
标准解读
在执行 instanceof (V instanceof target) 操作时,Es6 标准规定以下步骤:
- 判断 target 是否为对象,如果不是抛出 TypeError exception.
- let instOfHandler = GetMethod(target, @@hasInstance). // GetMethod 为外部的形象办法,获取对象的指定办法
- 如果 instOfHandler 不等于 undefined,返回调用 target 的 @@hasInstance 办法,并将后果返回 Boolean 值,算法完结。
<font color=”red”> 留神:这里会将后果值进行隐式转换 </font>
- 判断对象是否 IsCallable(能够看着是否是 Function 的实例), 如果不是抛出 TypeError exception.
- 这里进入 Es5 中对 instanceof 的标准,Es6 中称之为 OrdinaryHasInstance。
紧接着咱们看一下 OrdinaryHasInstance 是怎么规定的:
- 判断 target 是否 IsCallable,如果是下面算法进来的,必定是能够 Callable 的。
-
判断是否有 [[BoundTargetFunction]] 外部属性,如果有,let BC = target.[[BoundTargetFunction]],返回 V instanceof BC,算法完结。
<font color=”red”> 留神:这里的 [[BoundTargetFunction]] 其实是调用 bind 办法之前的原始办法 </font>
看上面的例子阐明:function F1(){} const F2 = F1.bind({}); const obj = new F2(); obj instanceof F1 // true
- 判断 V 是否为 Object,如果不是 返回 false。
- let P = target.prototype;
- 判断 P 是否为 Object,如果不是抛出 TypeError exception;
- 循环判断
let V = V.__proto__; if (V === null) {return false;} if(P === V){return true;}
默认值
Function.prototype[@@hasInstance] = function(V) {return OrdinaryHasInstance(this, V);
}
咱们能够看到在 es6 标准中,先尝试获取对象上的 @@hasInstance 办法,如果有,先调用对象上的 @@hasInstance 办法并返回。
Symbol.isConcatSpreadable
作用
@@isConcatSpreadable 用于在执行 Array.prototype.concat 时判断对象是否可开展。
咱们先看两个例子
class MyArray {constructor(){this.length = 0;}
push(val){this[this.length++] = val;
}
[Symbol.isConcatSpreadable] = true;
}
const array = new MyArray();
array.push(1);
array.push(2);
Array.prototype.concat.call(array, []); //[1,2] 这里主动开展 array
[].concat(array); // [1,2] 这里主动开展 array
class MyArrayNotConcatSpreadable {constructor(){this.length = 0;}
push(val){this[this.length++] = val;
}
}
const array2 = new MyArrayNotConcatSpreadable();
array2.push(1);
array2.push(2);
[].concat(array2); // [MyArrayNotConcatSpreadable 对象] 这里不会主动开展 array2
标准解读
@@isConcatSpreadable 用于 IsConcatSpreadable 形象办法,先看一下 IsConcatSpreadable(O)标准定义:
- 判读 O 是否为对象,如果不是返回 false.
- let spreadable = O[@@isConcatSpreadable].
- 如果 spreadable 不是 undefined,将其转换为 Boolean 值并返回。
- return IsArray(O).
IsConcatSpreadable 是形象办法,不会裸露给 javascript api,仅供外部调用,其用于 Array.prototype.concat 办法。
IsConcatSpreadable 在 Array.prototype.concat 中会产生如下作用:
- 依据以后调用对象类型生成新的数组,length 为 0,
- 循环以后调用对象和传入的 arguments 列表
- 调用 IsConcatSpreadable,判断以后是否可开展,如果可开展进行以下操作
- 取出以后值的 length,循环 k = 0 to length, 将每一项设置到第一步生成的新数组中。
伪代码如下
const O = ToObject(this.value);
const A = ArraySpeciesCreate(O, 0);
let n = 0;
for(item of [O, ...arguments]){if(IsConcatSpreadable(item)){
const length = item.length;
let k = 0;
while(k < length) {if(item.HasProperty(ToString(k))){Object.defineProperty(A, ToString(k), {value: item[ToString(k)]
});
}
}
}
}
留神:上述伪代码只是展现了 IsConcatSpreadable 的应用,并不是全副的 concat 算法逻辑
Symbol.match
作用
@@match 次要用于两个中央
- 用于正则判断,形象办法为 IsRegExp(argument)
- 用于 String.prototype.match, 自定义 match 逻辑
咱们还是联合例子看:
const helloWorldStartMatcher = {toString(){return 'Hello';}
}
'Hello World'.startsWith(helloWorldStartMatcher);// true
// startsWith 在这里会调用 helloWorldStartMatcher 的 toString 办法进行判断
helloWorldStartMatcher[Symbol.match] = function(){return true;}
'Hello World'.startsWith(helloWorldStartMatcher);// throw TypeError
// startsWith 调用时会调用 IsRegExp 对 helloWorldStartMatcher 进行判断,因为定义了 Symbol.match,所有返回 true,startsWith 会对正则抛出 TypeError
const helloWorldMatcher = {[Symbol.match](val){return 'Hello World'.indexOf(val);
}
}
'Hello'.match(helloWorldMatcher); // 0
helloWorldMatcher[Symbol.match] = function(){return /Hello/[Symbol.match](val);
};
'Hello World'.match(helloWorldMatcher); // 执行正则的 match 逻辑 等同于 'Hello World'.match(/Hello/);
标准解读
IsRegExp(argument)标准定义如下:
- 判断 argument 不是 Object,return false。
- let matcher = argument[@@match]
- 如果 matcher 不是 undefined, 将 matcher 转换为 Boolean 并返回.
- 如果 argument 有内置的 [[RegExpMatcher]] 属性, return true
- return false.
IsRegExp 次要用于 String.prototype.startsWith 和 String.prototype.endsWith,在这两个办法中会先通过 IsRegExp 对参数进行判断,如果是 true,会抛出 typeError 异样。
@@match 被 String.prototype.match (regexp)调用规定如下:
- 令 O 为以后对象的值。
- 如果 regexp 既不是 undefined 也不是 null,let matcher = GetMethod(regexp, @@match)。
- 如果 matcher 不是 undefined,返回 regexp[@@match]](O)。
留神:上述形容只是展现了 @@match 在标准中的作用,并不是全副的 String.prototype.match 算法逻辑
Symbol.replace
作用
@@replace 用于 String.prototype.replace,自定义 replace 逻辑
例子
const upperCaseReplacer = {[Symbol.replace](target, replaceValue){return target.replace('hello', replaceValue.toUpperCase());
}
}
'hello world'.replace(upperCaseReplacer, 'my');// MY world
标准解读
@@replace 被 String.prototype.replace (searchValue, replaceValue)调用规定如下:
- 令 O 为以后对象的值。
- 如果 searchValue 既不是 undefined 也不是 null,let replacer = GetMethod(searchValue, @@replace)。
- 如果 replacer 不是 undefined,返回 searchValue[@@replace]](O, replaceValue)。
留神:上述形容只是展现了 @@replace 在标准中的作用,并不是全副的 String.prototype.replace 算法逻辑
Symbol.search
作用
@@search 用于 String.prototype.search,自定义 search 逻辑
例子
const upperCaseSearcher = {
value: '',
[Symbol.search](target){return target.search(this.value.toUpperCase());
}
}
upperCaseSearcher.value = 'world';
'hello WORLD'.search(upperCaseSearcher);// 6
标准解读
@@search 被 String.prototype.search (regexp)调用规定如下:
- 令 O 为以后对象的值。
- 如果 regexp 既不是 undefined 也不是 null,let searcher = GetMethod(regexp, @@search)。
- 如果 searcher 不是 undefined,返回 regexp[@@search]](O)。
留神:上述形容只是展现了 @@search 在标准中的作用,并不是全副的 String.prototype.search 算法逻辑
Symbol.split
作用
@@split 用于 String.prototype.split,自定义 split 逻辑
例子
const upperCaseSplitter = {
value: '',
[Symbol.split](target, limit){return target.split(this.value.toUpperCase(), limit);
}
}
upperCaseSplitter.value = 'world';
'hello WORLD!'.split(upperCaseSplitter);// ["hello", "!"]
'hello WORLD!'.split(upperCaseSplitter, 1);// ["hello"]
标准解读
@@split 被 String.prototype.split (separator, limit)调用规定如下:
- 令 O 为以后对象的值。
- 如果 separator 既不是 undefined 也不是 null,let splitter = GetMethod(separator, @@split)。
- 如果 splitter 不是 undefined,返回 regexp[@@split]](O, limit)。
留神:上述形容只是展现了 @@split 在标准中的作用,并不是全副的 String.prototype.split 算法逻辑
Symbol.toStringTag
作用
@@toStringTag 通过 Object.prototype.toString 来调用的,用于形容对象。
例子
const obj = {[Symbol.toStringTag]: 'Hello'
}
Object.prototype.toString.call(obj); // "[object Hello]"
class ValidatorClass {}
Object.prototype.toString.call(new ValidatorClass()); // "[object Object]" 默认值
class ValidatorClass {get [Symbol.toStringTag]() {return "Validator";}
}
Object.prototype.toString.call(new ValidatorClass()); // "[object Validator]"
class ValidatorClass {get [Symbol.toStringTag]() {return {};
}
}
Object.prototype.toString.call(new ValidatorClass()); // "[object Object]"
标准解读
@@toStringTag 被 Object.prototype.toString 调用规定如下:
- 令 O 为以后对象的值。
- 先判断 null 和 undefined,满足条件返回 [object Null] 和[object Undefined]
- 顺次判断 Array, String, Arguments, Function, Error, Boolean, Number, Date, RegExp, Object,将对应的类型字段赋值给 builtinTag 变量
- let tag = O[@@toStringTag];
- 判断 tag,如果不是字符串,将 builtinTag 赋值给 tag
- 返回 ”[object “,tag,and”]”.
默认值
Es6 新增的 @@toStringTag 如下:
对象 | 值 |
---|---|
Atomics | Atomics |
Math | Math |
JSON | JSON |
Symbol.prototype | Symbol |
Map.prototype | Map |
Set.prototype | Set |
WeakMap.prototype | WeakMap |
WeakSet.prototype | WeakSet |
Promise.prototype | Promise |
ArrayBuffer.prototype | ArrayBuffer |
Module Namespace Objects | Module |
SharedArrayBuffer.prototype | SharedArrayBuffer |
DataView.prototype | DataView |
GeneratorFunction.prototype | GeneratorFunction |
AsyncGeneratorFunction.prototype | AsyncGeneratorFunction |
Generator.prototype | Generator |
AsyncGenerator.prototype | AsyncGenerator |
AsyncFunction.prototype | AsyncFunction |
%StringIteratorPrototype% | String Iterator |
%ArrayIteratorPrototype% | Array Iterator |
%MapIteratorPrototype% | Map Iterator (new Map()[Symbol.iterator]()) |
%SetIteratorPrototype% | Set Iterator |
%AsyncFromSyncIteratorPrototype% | Async-from-Sync Iterator |
Symbol.toPrimitive
作用
@@toPrimitive 被 ToPrimitive 形象办法调用,次要作用于类型转换。
咱们还是联合例子来看:
const obj = {[Symbol.toPrimitive](hint){if(hint === 'number') {return 2;}
return '1';
}
}
const keyObj = {'1': 1};
console.log(1 - obj);// -1 调用 ToNumber 类型转换
console.log(1 == obj); // true 形象相等算法时调用
console.log(obj + 1); // 11 + 号操作符时调用
console.log(keyObj[obj]); // 调用 ToPropertyKey 进行转换
console.log(0 < obj); // 形象比拟算法时调用
obj[Symbol.toPrimitive] = function(){return '2017-05-31'};
console.log(new Date(obj)); // Date 结构时调用
obj[Symbol.toPrimitive] = function(){return {}};
console.log(obj + 1);// throw type error
标准解读
因为 ToPrimitive 形象办法是 Es6 底层最次要的形象办法之一,调用点比拟多,咱们先重视看一下它的实现。
ToPrimitive (input [ , PreferredType] )被定义为如下:
- 判断以后 input 是否为 obj,如果不是,间接返回 input
- 依据 PreferredType 设置类型转换标识并赋值为 hint 变量,默认为 default
- 如果 PreferredType 是 Number,hint 赋值为 number,PreferredType 是 String,hint 赋值为 string。
-
let exoticToPrim = GetMethod(input, @@toPrimitive),如果 exoticToPrim 不是 undefined 进行如下操作
- 调用 input[@@toPrimitive](hint)并赋值给 result
- 如果 result 不是 Object 间接返回 result,否则抛出 type Error 异样
- 如果 hint 为 default,则赋值为 number
- 调用 OrdinaryToPrimitive(input, hint)
OrdinaryToPrimitive 为 Es5 标准定义的 ToPrimitive 办法,这里顺带介绍一下:
- 先判断 hint 是否为 string 或 number,如果都不是则抛出 TypeError 异样
- 如果 hint 是 string,则尝试先调用 toString,而后调用 valueOf
- 否则先尝试调用 valueOf,而后调用 toString。
- 以上两个办法如果都没有,或者调用返回后果都为 Object,则抛出 TypeError 异样
其次咱们看一下 ToPrimitive 调用点:
- ToNumber(input) 如果 input 是 Object 时,尝试调用 ToPrimitive(input, ‘number’)
- ToString(input) 如果 input 是 Object 时,尝试调用 ToPrimitive(input, ‘string’)
- ToPropertyKey(input) 尝试调用 ToPrimitive(input, ‘string’)
- 形象比拟时(例如:a < b),先尝试调用 ToPrimitive(input, ‘number’)
- 形象相等操作是(==),如果两边别离是 Number 和 String 类型或者其中一方为 Boolean 类型就会引起 ToNumber 调用,否则如果一方是 String, Number, 或者 Symbol 类型而另一方是 Object 类型,就会引起 ToPrimitive(Object 类型一方的值)
- 二元 + 号操作符会触发 ToPrimitive,ToString,ToNumber 动作
- Date 结构时,对于非 DateValue 类型的参数会触发 ToPrimitive
- Date.prototype.toJSON 会触发 ToPrimitive(thisValue, ‘number’)
- 其余但不限于调用 ToNumber 的操作,例如:++,–,+,- 等数字操作符,设置数组的 length,排序,Math.max(min), Number(value), isNaN 等。
- 调用 ToString 的操作设计 es 标准的方方面面,这里不一一赘述。
Symbol.species
作用
在 es 标准中,很多的办法都须要获取以后调用者的构造函数,而后依据此构造函数结构对象,可能这样说比拟形象,咱们还是先看例子吧。
class MyArray extends Array{
}
const array = new MyArray();
array.push(1);
array.push(2);
console.log(array instanceof Array); // true
console.log(array instanceof MyArray); // true
const mapArray = array.map(item => item);
console.log(mapArray instanceof Array); // true
console.log(mapArray instanceof MyArray); // true
从下面的例子中咱们看到,map 后的数组还是通过 MyArray 结构的,有时咱们心愿创立衍生对象时应用咱们指定的结构器。
class MyArray extends Array{static [Symbol.species] = Array;
// 等同于下面成果
//static get [Symbol.species](){
// return Array;
//}
}
const array = new MyArray();
array.push(1);
array.push(2);
console.log(array instanceof Array); // true
console.log(array instanceof MyArray); // true
const mapArray = array.map(item => item);
console.log(mapArray instanceof Array); // true
console.log(mapArray instanceof MyArray); // false
标准解读
在 es6 标准中,Symbol.species 扩大属性次要作用于两个形象动作中,别离是 SpeciesConstructor,ArraySpeciesCreate,咱们先来看看这两个形象动作具体是如何执行的。
SpeciesConstructor (O, defaultConstructor)定义如下:
其中 O 是以后的调用者,如果 O 中不存在 @@species 属性就以 defaultConstructor 为默认结构器
- let C = O.constructor。
- 如果 C 是 undefined,返回 defaultConstructor。
- 如果 C 不是对象,抛出 TypeError
- let S = O[@@species]
- 如果 S 为 null 或者 undefined,返回 defaultConstructor。
- 调用 IsConstructor(S),判断 S 是否为结构器,如果是返回 S.
- 抛出 TypeError
ArraySpeciesCreate (originalArray, length)定义如下:
其中 originalArray 是以后的调用数组
- let isArray = IsArray(originalArray)。
- 如果 isArray 为 false, return new Array(length)。
- let C = originalArray.constructor
- 如果 C 是结构器
判断 C 和以后的全局环境对应 Array 结构器是否雷同,如果不雷同将 C 置为 undefined (避免跨 window 创建对象) -
如果 C 是 Object
- C = C[@@species]
- 如果 C 为 null,重置为 undefined
- 如果 C 是 undefined,return new Array(length)。
- 如果 C 不是结构器, 抛出 TypeError.
- 基于 C 创立数组,长度为 length。
注:上述是标准的简化过程,去除了一些断言和判断
咱们看一下 SpeciesConstructor 调用点:
- 调用正则原型上的 [Symbol.split] 办法(调用字符串的 split 办法时会调用传入正则的 [Symbol.split] 办法)
- 创立 TypedArray 时触发(其中还包含 TypedArray 的 slice,subarray,map 办法)
- [Shared]ArrayBuffer.prototype.slice 被调用时触发
- Promise.prototype.then 或 finally 时被触发
例如
class MyPromise extends Promise {
}
const thenMyPromise = MyPromise.resolve().then();
console.log(thenMyPromise instanceof MyPromise); // true
console.log(thenMyPromise instanceof Promise); // true
class MyPromise2 extends Promise {static get [Symbol.species]() {return Promise;}
}
const thenMyPromise2 = MyPromise2.resolve().then();
console.log(thenMyPromise2 instanceof MyPromise); // false
console.log(thenMyPromise2 instanceof Promise); // true
ArraySpeciesCreate 调用点:
次要用于 Array 原型上的办法时调用触发,包含 concat, filter, flat,map,slice,splice 办法
默认值
es6 标准中定义的 javascript 原始类型的 @@species 默认值为 Return the this value.
Symbol.iterator
作用
这个可能是自定义时应用的最多的,它能够帮忙咱们自定义迭代器,而且 ECMAScript 标准中的 Set,Map 等迭代过程都是基于它实现的。
在 Typescript 的 Es6 签名库,咱们能够看到迭代器的签名如下:
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Iterator<T, TReturn = any, TNext = undefined> {next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
interface Iterable<T> {[Symbol.iterator](): Iterator<T>;}
通过签名咱们能够看到实现自定义迭代器须要扩大 [Symbol.iterator] 办法,而该办法要返回一个 Iterator,Iterator 中的 next 办法承受一个值,返回 IteratorResult。其中的 return 办法的应用场合是,如果 for…of 循环提前退出(通常是因为出错,或者有 break 语句),就会调用 return 办法。
throw 办法,能够在函数体外抛出谬误,而后在 Generator 函数体内捕捉, 次要是配合 Generator 应用。
咱们先看两个例子感受一下。
function *iterable () {
yield 1;
yield 2;
yield 3;
};
// iterable()返回一个迭代器
for(const val of iterable()){console.log(val);
// 输入 1,2,3
}
class EvenArray extends Array {[Symbol.iterator](){
const _this = this;
let index = 0;
return {next(){if(index < _this.length){const value = _this[index];
index += 2;
return {
done: false,
value,
}
}
return {done: true};
},
return() {
this._index = 0;
console.log('return iterator');
return {done: true}
}
}
}
}
const array = new EvenArray();
for(let i = 0; i <= 100; i++){array.push(i);
}
for(const val of array){console.log(val); // 0, 2, 4, 6, ... , 98, 100
}
for(const val of array){console.log(val); // 0
// return iterator 调用了 return 办法
break;
}
for(const val of array){console.log(val); // 0
// return iterator 调用了 return 办法
throw new Error();}
// // 等同于下面代码
// class EvenArray extends Array {// constructor(){// super();
// this.index = 0;
// }
// [Symbol.iterator](){
// this.index = 0;
// return this;
// }
// next(){// if(this.index < this.length){// const value = this[this.index];
// this.index += 2;
// return {
// done: false,
// value,
// }
// }
// return {
// done: true
// };
// }
// }
const myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
// 扩大默认调用迭代器
console.log([...myIterable]); // [1, 2, 3]
function *iterable2 () {yield* myIterable; // 悬停 myIterable 迭代器};
for(const val of iterable2()){console.log(val); // 1,2,3
}
function consoleArgs(...args){console.log(args);
}
consoleArgs(...myIterable);// 残余参数调用默认调用迭代器
标准解读
先梳理一下 @@iterator 的调用点:
- 调用形象办法 GetIterator (obj [ , hint [ , method] ] )时,其中 hint 取值为 async 或 sync,默认为 sync。method 为指定的返回迭代器的办法
- 调用形象办法 CreateUnmappedArgumentsObject 和 CreateMappedArgumentsObject 时(这里次要是解决 arguments 时调用)
- 调用 Array.from 时
- 调用 %TypedArray%.from 时
咱们一个个来分析外面的具体实现及其作用
-
GetIterator (obj [ , hint [ , method] ] )定义如下
- 如果 hint 为 undefined,则重置为 sync
-
如果 method 没有提供,进行如下操作
-
如果 hint 为 async
- method = GetMethod(obj, @@asyncIterator).
-
如果 method 为 undefined,则
- let syncMethod = GetMethod(obj, @@iterator)
- let syncIteratorRecord = GetIterator(obj, sync, syncMethod)
- return CreateAsyncFromSyncIterator(syncIteratorRecord)。//CreateAsyncFromSyncIterator 为形象办法,用于通过 Iterator 创立异步迭代器。
- method = GetMethod(obj, @@iterator)
-
- let iterator = obj.method();
- 判断如果 iterator 不是 Object,抛出 TypeError
- let nextMethod = iterator.next;
- let iteratorRecord = {[[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }
- return iteratorRecord;
通过上述算法咱们能够看到,GetIterator 最终返回一个包装好的迭代器对象。那么都有那些中央调用 GetIterator 形象办法呢?
- 扩大数组时,let array = [1, 2, …array2];
- 解构数组时,let [one] = array;
- rest 参数解决时,function gen(…args){}; gen(…array);
- 参数解构绑定时,function gen([one]){}; gen(array);
- yield 调用时,function gen() { yield* array};
- Array.from 调用时,Array.from(array)。
- new Set,new Map 调用时(其中包含 WeakSet 和 WeakMap),new Set(array)。
- Promise.all|race 调用时,Promise.all(array)。
- for of 调用时。
因为迭代器波及的调用点比拟多,可能须要独自的一篇文档介绍,这里重视看一下 for of 的标准:
for of 执行次要蕴含两个局部:
- 调用 ForIn/OfHeadEvaluation 形象办法,返回迭代器
- 调用 ForIn/OfBodyEvaluation 执行迭代器
接下来看一下 ForIn/OfHeadEvaluation(TDZnames, expr, iterationKind)的标准定义:
阐明:该形象办法有三个参数,别离示意:绑定的环境变量名称、of 前面的语句、迭代的类型(包含 enumerate、async-iterate、iterate)。具体含意及其作用咱们接着往下看。
- 设置 oldEnv 为以后执行环境
-
如果 TDZnames 不为空,执行如下操作
- TDZ 为应用 oldEnv 创立的新的申明式环境
- TDZEnvRec 设置为 TDZ 的环境记录项
- 将 TDZnames 绑定到 TDZEnvRec 上
- 将以后执行上下文的词法环境设置为 TDZ
- 设置 exprValue 为 expr 执行后的值
-
判断 iterationKind 是否为 enumerate,如果是(这里次要用于 for in)
- 如果 exprValue 为 null 或者 undefined,return Completion{[[Type]]: break, [[Value]]: empty, [[Target]]: empty }(这是 es 标准中的一种类型,用来管制 break, continue, return 和 throw,在这里能够看作跳出循环)
- let obj = ToObject(exprValue)
- return EnumerateObjectProperties(obj) // EnumerateObjectProperties 用于循环对象,返回对象迭代器,这里不展开讨论
-
否则
- 判断 iterationKind 是否为 async-iterate,如果是设置变量 iteratorHint 为 async
- 否则 iteratorHint 为 sync
- 调用 GetIterator(exprValue, iteratorHint)获取迭代器并返回
上述办法返回的后果会传入到 ForIn/OfBodyEvaluation 进行变量执行
ForIn/OfBodyEvaluation (lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind]) 标准定义如下:
参数比拟多,咱们一个一个解释:
- lhs:of 后面的申明语句
- stmt:for of 循环体
- iteratorRecord:上文中返回的迭代器
- iterationKind:迭代的类型(同上文)
- lhsKind:变量绑定类型(assignment, varBinding 或者 lexicalBinding)
- labelSet:管制语句(例如 return,break,continue)
- iteratorKind:迭代器类型(用于标识异步迭代器 async)
算法执行逻辑如下:
- 如果 iteratorKind 为空,设置为 sync
- 用 oldEnv 变量示意以后执行上下文的词法环境
- 申明一个 V 变量,设为 undefined
- 如果 lhs 是解构语句,对解构语句进行解决
-
开始进入循环
- let nextResult = iteratorRecord.[[Iterator]][iteratorRecord.[[NextMethod]]]();
- 如果 iteratorKind 为 async,nextResult = Await(nextResult)(异步迭代器,应用 await 悬停)
- 通过 IteratorComplete(nextResult)判断是否迭代实现(这里其实就是判断的 done 是否为 true)
- 如果 done 为 true,return NormalCompletion(V)(这里和上文中的 Completion 作用类似,能够看作是跳出循环)
- let nextValue = IteratorValue(nextResult) (获取迭代器执行返回的 value)
- 这里次要是依据 lhsKind 解析 lhs 获取对应的变量绑定援用(标准形容的太具体,咱们这里先理解其作用)
- 下面绑定变量时会返回 status 用于形容执行后的状态,如果 status 不是 NormalCompletion(例如出现异常),则判断 iterationKind,如果 iterationKind 是 enumerate 间接返回 status,否则返回 iteratorRecord.[[Iterator]][iteratorRecord.[[ReturnMethod]]]()
- 设置 result 为执行 stmt 的后果(result 也是一个 Completion)
- 判断 result 后果是否可持续循环(例如 break,return 等语句会跳出循环),如果不能够,则判断 iterationKind,如果 iterationKind 是 enumerate 间接返回 status,否则返回 iteratorRecord.[[Iterator]][[iteratorRecord[[ReturnMethod]]]()
- 如果 result.[[Value]]不为空,则 V = result.[[Value]]
上述算法去除了标准里的一些繁琐的步骤,尤其是 lhs 解析绑定的局部,如果想要深刻理解,倡议查看 ECMAScript 标准文档。
默认值
Es6 内置的少数对象都实现来迭代器,具体如下:
- String.prototype [@@iterator]
- Array.prototype [@@iterator]
- %TypedArray%.prototype [@@iterator]
- Map.prototype [@@iterator]
- Set.prototype [@@iterator]
- %IteratorPrototype% [@@iterator]
Symbol.asyncIterator(@@asyncIterator)
作用
Symbol.asyncIterator 指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于 for await…of 循环。
接下来咱们看几个例子:
- 例子 1:
const myAsyncIterable = new Object();
myAsyncIterable[Symbol.asyncIterator] = async function*() {
yield 1;
yield 2;
yield 3;
};
(async () => {for await (const x of myAsyncIterable) {console.log(x);
// 输入:
// 1
// 2
// 3
}
})();
当然也能够通过它遍历 promise
- 例子 2:
const myAsyncIterable = new Object();
const promise1 = new Promise(resolve=>setTimeout(() => resolve(1), 500));
const promise2 = Promise.resolve(2);
myAsyncIterable[Symbol.asyncIterator] = async function*() {
yield await promise1;
yield await promise2;
};
(async () => {for await (const x of myAsyncIterable) {console.log(x);
// 输入:
// 1
// 2
}
})();
也能够自定义异步迭代器
- 例子 3:
const myAsyncIterable = {
promiseList:[new Promise(resolve=>setTimeout(() => resolve(1), 500)),
Promise.resolve(2)
],
[Symbol.asyncIterator](){
const _this = this;
let index = 0;
return {next(){if(index === _this.promiseList.length){return Promise.resolve({done: true});
}
return _this.promiseList[index++].then(value => ({done: false, value}))
}
}
}
};
(async () => {for await (const x of myAsyncIterable) {console.log(x);
// 输入:
// 1
// 2
}
})();
标准解读
@@asyncIterator 作用和 @@iterator,在标准定义中也是对立解决的,只是在执行 ForIn/OfBodyEvaluation 时 iteratorKind 参数设置为了 async,执行函数时通过 Await 动作解决 @@asyncIterator。
Symbol.unscopables(@@unscopables)
作用
对象的 Symbol.unscopables 属性,指向一个对象。该对象指定了应用 with 关键字时,哪些属性会被 with 环境排除
const object1 = {property1: 42};
object1[Symbol.unscopables] = {property1: true};
with (object1) {console.log(property1);
// expected output: Error: property1 is not defined
}
标准解读
@@unscopables 用于 HasBinding 调用
HasBinding 查看对象是否绑定到以后的环境记录项中,标准中的 HasBinding 最初会通过 @@unscopables 进行过滤。
默认值
标准中只有 Array.prototype 指定了 @@unscopables
具体如下:
{
"copyWithin":true,
"entries":true,
"fill":true,
"find":true,
"findIndex":true,
"flat":true,
"flatMap":true,
"includes":true,
"keys":true,
"values":true
}