你不知道的Symbol内置值的规范解读

首先先熟悉一下Symbol类型的定义及其做用:javascript

  • 能够用做对象属性键的非字符串值的集合。
  • 每一个Symbol值都是唯一且不可变的。
  • 每一个Symbol值都与一个[[Description]]的值关联,该值要么是undefined,要么是一个字符串。

内置的Symbol值主要是用于ECMAScript规范算法扩展的。本文主要是经过对规范的解读来了解Symbol内置的值是如何使用及其规范定义的。java

咱们先浏览一下规范里的Symbol内置的值:es6

规范名称 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方法。typescript

咱们来看一个例子json

class MyArray {
  static [Symbol.hasInstance](val){
    return val instanceof Array;
  }
}

[1,2,3] instanceof MyArray; // true

规范解读

在执行instanceof (V instanceof target) 操做时,Es6规范规定如下步骤:api

  1. 判断target是否为对象,若是不是抛出TypeError exception.
  2. let instOfHandler = GetMethod(target, @@hasInstance). // GetMethod为内部的抽象方法,获取对象的指定方法
  3. 若是instOfHandler不等于undefined,返回调用target的@@hasInstance方法,并将结果返回Boolean值,算法结束。

    <font color="red">注意:这里会将结果值进行隐式转换</font>数组

  4. 判断对象是否IsCallable(能够看着是不是Function的实例), 若是不是抛出TypeError exception.
  5. 这里进入Es5中对instanceof的规范,Es6中称之为OrdinaryHasInstance。

紧接着咱们看一下OrdinaryHasInstance是怎么规定的:promise

  1. 判断target是否IsCallable,若是是上面算法进来的,确定是能够Callable的。
  2. 判断是否有[[BoundTargetFunction]]内部属性,若是有, let BC = target.[[BoundTargetFunction]],返回 V instanceof BC, 算法结束。app

    <font color="red">注意: 这里的[[BoundTargetFunction]]实际上是调用bind方法以前的原始方法</font>
    看下面的例子说明:

    function F1(){}
    const F2 = F1.bind({});
    const obj = new F2();
    obj instanceof F1 // true
    1. 判断V是否为Object,若是不是 返回false。
    2. let P = target.prototype;
    3. 判断P是否为Object,若是不是抛出TypeError exception;
    4. 循环判断
    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)规范定义:

  1. 判读O是否为对象,若是不是返回false.
  2. let spreadable = O[@@isConcatSpreadable].
  3. 若是spreadable不是undefined,将其转换为Boolean值并返回。
  4. return IsArray(O).

IsConcatSpreadable是抽象方法,不会暴露给javascript api,仅供内部调用,其用于Array.prototype.concat方法。

IsConcatSpreadable在Array.prototype.concat中会产生以下做用:

  1. 根据当前调用对象类型生成新的数组,length为0,
  2. 循环当前调用对象和传入的arguments列表
  3. 调用IsConcatSpreadable,判断当前是否可展开,若是可展开进行如下操做
  4. 取出当前值的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)规范定义以下:

  1. 判断argument不是Object,return false。
  2. let matcher = argument[@@match]
  3. 若是matcher不是undefined, 将matcher转换为Boolean并返回.
  4. 若是argument有内置的[[RegExpMatcher]]属性, return true
  5. return false.

IsRegExp主要用于String.prototype.startsWith和String.prototype.endsWith,在这两个方法中会先经过IsRegExp对参数进行判断,若是是true,会抛出typeError异常。

@@match被String.prototype.match ( regexp )调用规则以下:

  1. 令O为当前对象的值。
  2. 若是regexp既不是undefined也不是null,let matcher = GetMethod(regexp, @@match)。
  3. 若是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 )调用规则以下:

  1. 令O为当前对象的值。
  2. 若是searchValue既不是undefined也不是null,let replacer = GetMethod(searchValue, @@replace)。
  3. 若是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)调用规则以下:

  1. 令O为当前对象的值。
  2. 若是regexp既不是undefined也不是null,let searcher = GetMethod(regexp, @@search)。
  3. 若是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 )调用规则以下:

  1. 令O为当前对象的值。
  2. 若是separator既不是undefined也不是null,let splitter = GetMethod(separator, @@split)。
  3. 若是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调用规则以下:

  1. 令O为当前对象的值。
  2. 先判断null和undefined,知足条件返回[object Null]和[object Undefined]
  3. 依次判断Array, String, Arguments, Function, Error, Boolean, Number, Date, RegExp, Object,将对应的类型字段赋值给builtinTag变量
  4. let tag = O[@@toStringTag];
  5. 判断tag,若是不是字符串,将builtinTag赋值给tag
  6. 返回"[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 ] )被定义为以下:

  1. 判断当前input是否为obj,若是不是,直接返回input
  2. 根据PreferredType设置类型转换标识并赋值为hint变量,默认为default
  3. 若是PreferredType是Number,hint赋值为number,PreferredType是String,hint赋值为string。
  4. let exoticToPrim = GetMethod(input, @@toPrimitive),若是exoticToPrim不是undefined进行以下操做

    1. 调用input[@@toPrimitive](hint)并赋值给result
    2. 若是result不是Object直接返回result,不然抛出type Error异常
  5. 若是hint为default,则赋值为number
  6. 调用OrdinaryToPrimitive( input, hint )

OrdinaryToPrimitive为Es5规范定义的ToPrimitive方法,这里顺带介绍一下:

  1. 先判断hint是否为string或number,若是都不是则抛出TypeError异常
  2. 若是hint是string,则尝试先调用toString,而后调用valueOf
  3. 不然先尝试调用valueOf,而后调用toString。
  4. 以上两个方法若是都没有,或者调用返回结果都为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为默认构造器

  1. let C = O.constructor。
  2. 若是C是undefined,返回defaultConstructor。
  3. 若是C不是对象,抛出TypeError
  4. let S = O[@@species]
  5. 若是S为null或者undefined,返回defaultConstructor。
  6. 调用IsConstructor(S),判断S是否为构造器,若是是返回S.
  7. 抛出TypeError

ArraySpeciesCreate ( originalArray, length )定义以下:
其中originalArray是当前的调用数组

  1. let isArray = IsArray(originalArray)。
  2. 若是isArray为false, return new Array(length)。
  3. let C = originalArray.constructor
  4. 若是C是构造器
    判断C和当前的全局环境对应Array构造器是否相同,若是不相同将C置为 undefined (防止跨window建立对象)
  5. 若是C是Object

    1. C = C[@@species]
    2. 若是C为null,重置为undefined
  6. 若是C是undefined,return new Array(length)。
  7. 若是C不是构造器, 抛出TypeError.
  8. 基于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 时

咱们一个个来剖析里面的具体实现及其做用

  1. GetIterator ( obj [ , hint [ , method ] ] )定义以下

    1. 若是hint为undefined,则重置为sync
    2. 若是method没有提供,进行以下操做

      1. 若是hint为async

        1. method = GetMethod(obj, @@asyncIterator).
        2. 若是method为undefined,则

          1. let syncMethod = GetMethod(obj, @@iterator)
          2. let syncIteratorRecord = GetIterator(obj, sync, syncMethod)
          3. return CreateAsyncFromSyncIterator(syncIteratorRecord)。//CreateAsyncFromSyncIterator为抽象方法,用于经过Iterator建立异步迭代器。
      2. method = GetMethod(obj, @@iterator)
    3. let iterator = obj.method();
    4. 判断若是iterator不是Object,抛出TypeError
    5. let nextMethod = iterator.next;
    6. let iteratorRecord = { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }
    7. 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执行主要包含两个部分:

  1. 调用ForIn/OfHeadEvaluation抽象方法,返回迭代器
  2. 调用ForIn/OfBodyEvaluation执行迭代器

接下来看一下ForIn/OfHeadEvaluation(TDZnames, expr, iterationKind )的规范定义:
说明:该抽象方法有三个参数,分别表示:绑定的环境变量名称、of后面的语句、迭代的类型(包括enumerate、async-iterate、iterate)。具体含义及其做用咱们接着往下看。

  1. 设置oldEnv为当前执行环境
  2. 若是TDZnames不为空,执行以下操做

    1. TDZ 为使用oldEnv建立的新的声明式环境
    2. TDZEnvRec 设置为TDZ的环境记录项
    3. 将TDZnames绑定到TDZEnvRec上
    4. 将当前执行上下文的词法环境设置为TDZ
  3. 设置exprValue为expr执行后的值
  4. 判断iterationKind是否为enumerate,若是是(这里主要用于for in)

    1. 若是exprValue为null或者undefined,return Completion{ [[Type]]: break, [[Value]]: empty, [[Target]]: empty } (这是es规范中的一种类型,用来控制break, continue, return 和 throw, 在这里能够看做跳出循环)
    2. let obj = ToObject(exprValue)
    3. return EnumerateObjectProperties(obj) // EnumerateObjectProperties用于循环对象,返回对象迭代器,这里不展开讨论
  5. 不然

    1. 判断iterationKind是否为async-iterate,若是是设置变量iteratorHint为async
    2. 不然 iteratorHint 为sync
    3. 调用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)

算法执行逻辑以下:

  1. 若是iteratorKind为空,设置为sync
  2. 用oldEnv变量表示当前执行上下文的词法环境
  3. 声明一个V变量,设为undefined
  4. 若是lhs是解构语句,对解构语句进行处理
  5. 开始进入循环

    1. let nextResult = iteratorRecord.[[Iterator]][iteratorRecord.[[NextMethod]]]();
    2. 若是iteratorKind为async,nextResult = Await(nextResult)(异步迭代器,使用await悬停)
    3. 经过IteratorComplete(nextResult)判断是否迭代完成(这里其实就是判断的done是否为true)
    4. 若是done为true,return NormalCompletion(V) (这里和上文中的Completion做用类似,能够看做是跳出循环)
    5. let nextValue = IteratorValue(nextResult) (获取迭代器执行返回的value)
    6. 这里主要是根据lhsKind解析lhs获取对应的变量绑定引用(规范描述的太详细,咱们这里先了解其做用)
    7. 上面绑定变量时会返回status用于描述执行后的状态,若是status不是NormalCompletion(例如出现异常),则判断iterationKind,若是iterationKind是enumerate直接返回status,不然返回iteratorRecord.[[Iterator]][iteratorRecord.[[ReturnMethod]]]()
    8. 设置result为执行stmt的结果(result也是一个Completion)
    9. 判断result结果是否可继续循环(例如break, return等语句会跳出循环),若是不能够,则判断iterationKind,若是iterationKind是enumerate直接返回status,不然返回iteratorRecord.[[Iterator]][[iteratorRecord[[ReturnMethod]]]()
    10. 若是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
}
相关文章
相关标签/搜索