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

首先先熟悉一下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规范规定以下步骤:

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

    注意:这里会将结果值进行隐式转换

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

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

  1. 判断target是否IsCallable,如果是上面算法进来的,肯定是可以Callable的。
  2. 判断是否有[[BoundTargetFunction]]内部属性,如果有, let BC = target.[[BoundTargetFunction]],返回 V instanceof BC, 算法结束。

    注意: 这里的[[BoundTargetFunction]]其实是调用bind方法之前的原始方法
    看下面的例子说明:

    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 {
    done: true;
    value: TReturn;
}

interface IteratorYieldResult {
    done?: false;
    value: TYield;
}

type IteratorResult = IteratorYieldResult | IteratorReturnResult;

interface Iterator {
    next(...args: [] | [TNext]): IteratorResult;
    return?(value?: TReturn): IteratorResult;
    throw?(e?: any): IteratorResult;
}

interface Iterable {
    [Symbol.iterator](): Iterator;
}

通过签名我们可以看到实现自定义迭代器需要扩展[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
}

你可能感兴趣的:(es6,ecmascript,javascript,前端)