关于es6:你不知道的Symbol内置值的规范解读

38次阅读

共计 19543 个字符,预计需要花费 49 分钟才能阅读完成。

首先先相熟一下 Symbol 类型的定义及其作用:

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

内置的 Symbol 值次要是用于 ECMAScript 标准算法扩大的。本文次要是通过对标准的解读来理解 Symbol 内置的值是如何应用及其标准定义的。

咱们先浏览一下标准里的 Symbol 内置的值:

标准名称 Description 值及其作用
@@asyncIterator “Symbol.asyncIterator” 一个返回异步迭代器的办法,次要用于 for await
@@hasInstance “Symbol.hasInstance” 用于确认对象是否为该构造函数实例的办法,次要用于 instanceof
@@isConcatSpreadable “Symbol.isConcatSpreadable” 一个 Boolean 值,标识是否能够通过 Array.prototype.concat 进行扁平化解决
@@iterator “Symbol.iterator” 一个返回异步迭代器的办法,次要用于 for of
@@match “Symbol.match” 用于 String.prototype.match 调用
@@replace “Symbol.replace” 用于 String.prototype.replace 调用
@@search “Symbol.search” 用于 String.prototype.search 调用
@@species “Symbol.species” 一个用来返回创立派生对象的构造函数的办法
@@split “Symbol.split” 用于 String.prototype.split 调用
@@toPrimitive “Symbol.toPrimitive” 用于 ToPrimitive 形象办法
@@toStringTag “Symbol.toStringTag” 用于形容一个对象的字符串,次要用于 Object.prototype.toString 调用
@@unscopables “Symbol.unscopables” 用于 with 环境绑定中排除的属性名称

下面有些形容比拟形象,不要急,咱们将一一来认真理解其标准定义和作用

Symbol.hasInstance(@@hasInstance)

作用

和下面形容的一样,用于确认对象是否为该构造函数实例的办法,次要用于 instanceof, 当调用 instanceof 时,外部办法会调用对象上的 Symbol.hasInstance 办法。

咱们来看一个例子

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

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

标准解读

在执行 instanceof (V instanceof target) 操作时,Es6 标准规定以下步骤:

  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 是怎么规定的:

  1. 判断 target 是否 IsCallable,如果是下面算法进来的,必定是能够 Callable 的。
  2. 判断是否有 [[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
    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
}

正文完
 0