首先先相熟一下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] 这里主动开展arrayclass 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); // 0helloWorldMatcher[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); // trueconsole.log(array instanceof MyArray); // trueconst mapArray = array.map(item => item);console.log(mapArray instanceof Array); // trueconsole.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); // trueconsole.log(array instanceof MyArray); // trueconst mapArray = array.map(item => item);console.log(mapArray instanceof Array); // trueconsole.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); // trueconsole.log(thenMyPromise instanceof Promise); // trueclass MyPromise2 extends Promise { static get [Symbol.species]() { return Promise; }}const thenMyPromise2 = MyPromise2.resolve().then();console.log(thenMyPromise2 instanceof MyPromise); // falseconsole.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}