你不知道的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
}
相關文章
相關標籤/搜索