关于es6:你不知道的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值,算法完结。

    <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
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理