First of all, familiarize yourself with the definition and function of Symbol type:

  • A collection of non-string values that can be used as object property keys.
  • Each Symbol value is unique and immutable.
  • Each Symbol value is associated with a [[Description]] value, which is either undefined or a string.

The built-in Symbol value is primarily used for algorithm extensions to the ECMAScript specification. This article is mainly through the interpretation of the specification to understand how Symbol built-in value is used and its specification definition.

Let’s first look at the built-in value of Symbol in the specification:

The name of the specification Description Value and its function
@@asyncIterator “Symbol.asyncIterator” A method that returns an asynchronous iterator, mainly for await
@@hasInstance “Symbol.hasInstance” The method used to verify that an object is an instanceof the constructor, primarily for instanceof
@@isConcatSpreadable “Symbol.isConcatSpreadable” A Boolean value that identifies whether flattening can be done with array.prototype. concat
@@iterator “Symbol.iterator” A method that returns an asynchronous iterator, used primarily for of
@@match “Symbol.match” For string.prototype. match calls
@@replace “Symbol.replace” For string.prototype. replace calls
@@search “Symbol.search” Used for the string.prototype. search call
@@species “Symbol.species” A method used to return a constructor that creates a derived object
@@split “Symbol.split” For string.prototype.split calls
@@toPrimitive “Symbol.toPrimitive” For ToPrimitive abstract methods
@@toStringTag “Symbol.toStringTag” Object is used to describe a string, it is mainly used for the Object. The prototype. ToString calls
@@unscopables “Symbol.unscopables” The name of the property used to exclude in the with environment binding

Some of the descriptions above are abstract, so don’t worry, we’ll take a closer look at their specification definitions and functions one by one

Symbol.hasInstance(@@hasInstance)

role

The methods used to confirm that an object is an instanceof the constructor, as described above, are primarily used for instanceof, and when instanceof is called, the internal method calls the symbol.hasinstance method on the object.

Let’s look at an example

class MyArray { static [Symbol.hasInstance](val){ return val instanceof Array; }} [2,3] instanceof MyArray; // trueCopy the code

Normative interpretation

When performing an instanceof (V instanceof Target) operation, the Es6 specification specifies the following steps:

  1. Determines whether target is an object, and if not raises a TypeError Exception.

  2. Let instOfHandler = GetMethod(target, @@hasInstance). // GetMethod is an internal abstract method

  3. If instOfHandler is not equal to undefined, call target’s @@hasInstance method and return Boolean. The algorithm ends.

    Note: The resulting value is implicitly converted

  4. Check if the object is IsCallable (see if it is an instance of Function), and if it does not throw a TypeError Exception.

  5. Here comes the Es5 specification for Instanceof, which in Es6 is called OrdinaryHasInstance.

Then let’s look at how OrdinaryHasInstance is specified:

  1. Check whether the target is IsCallable. If the algorithm above is used, it must be Callable.

  2. Let BC = target.[[BoundTargetFunction]], return V instanceof BC, end of algorithm.

    [[BoundTargetFunction]] is the original method before calling bind.

    function F1(){}
    const F2 = F1.bind({});
    const obj = new F2();
    obj instanceof F1 // true
    Copy the code
  3. Check whether V is Object, if not return false.

  4. let P = target.prototype;

  5. Check whether P is Object, if not raise TypeError Exception;

  6. Cycle judgment

let V = V.__proto__;
if (V === null) {
	return false;
}
if(P === V){
	return true;
}
Copy the code

The default value

Function.prototype[@@hasInstance] = function(V) {
    return OrdinaryHasInstance(this, V);
}
Copy the code

We can see that in the ES6 specification, we first try to get the @@hasInstance method on an object, and if so, call the @@hasInstance method on the object and return it.

Symbol.isConcatSpreadable

role

@@isconcatspreadable is used to determine if an object can be expanded when array.prototype. concat is executed. Let’s look at two examples

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] here the array is automatically expanded
[].concat(array); // [1,2] here the array is automatically expanded

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 object array2 here will not automatically
Copy the code

Normative interpretation

@@isconcatspreadable Is used to abstract isConcatSpreadable. Let’s take a look at isConcatSpreadable (O) specification definitions:

  1. Check whether O is an object, if not return false.
  2. let spreadable = O[@@isConcatSpreadable].
  3. If spreadable is not undefined, convert it to Boolean and return it.
  4. return IsArray(O).

IsConcatSpreadable is an abstract method that is not exposed to javascript apis and is only used internally for array.prototype. concat methods.

IsConcatSpreadable has the following functions in array.prototype. concat:

  1. Generates a new array based on the current called object type, length is 0,
  2. Loop over the currently called object and the arguments list passed in
  3. Call IsConcatSpreadable to determine whether it is currently spreadable. If so, perform the following operations
  4. Take the length of the current value, loop k = 0 to length, and set each item to the new array generated in the first step.

The pseudocode is as follows

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(n), {
                    value: item[ToString(k)] }); } k++; n++; }}}Copy the code

Note: The above pseudocode only shows the use of IsConcatSpreadable, not the entire concat algorithm logic

Symbol.match

role

@@Match is used in two main places

  • The argument method is IsRegExp(argument)
  • String.prototype.match, custom match logic

Let’s look at some examples:

const helloWorldStartMatcher = {
    toString(){
        return 'Hello'; }}'Hello World'.startsWith(helloWorldStartMatcher);// true  
// startsWith here calls the toString method of helloWorldStartMatcher to determine

helloWorldStartMatcher[Symbol.match] = function(){
    return true;
}
'Hello World'.startsWith(helloWorldStartMatcher);// throw TypeError
// startsWith calls IsRegExp to determine helloWorldStartMatcher because symbol. match is defined, all return true, and startsWith raises TypeError on the re
Copy the code
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); // Execute the match logic of the re equal to 'Hello World'. Match (/Hello/);
Copy the code

Normative interpretation

The IsRegExp(argument) specification is defined as follows:

  1. Argument is not Object, return false.
  2. let matcher = argument[@@match]
  3. If matcher is not undefined, convert matcher to Boolean and return.
  4. Return true if argument has a built-in [[RegExpMatcher]] property
  5. return false.

IsRegExp is mainly used for String. Prototype. StartsWith and String prototype. EndsWith, in these two methods will be through IsRegExp to determine the parameters, and if it is true, will be thrown typeError anomalies.

@@match is called by string.prototype. match (regexp) as follows:

  1. Let O be the value of the current object.
  2. If regexp is neither undefined nor null, let matcher = GetMethod(regexp, @@match).
  3. If matcher is not undefined, return regexp[@@match]](O).

Note: the above description only shows the role of @@match in the specification, not the whole String. Prototype. match algorithm logic

Symbol.replace

role

@@replace is used for string.prototype. replace, custom replace logic

example

const upperCaseReplacer = {
    [Symbol.replace](target, replaceValue){
        return target.replace('hello', replaceValue.toUpperCase()); }}'hello world'.replace(upperCaseReplacer, 'my');// MY world

Copy the code

Normative interpretation

@@replace is called by string.prototype. replace (searchValue, replaceValue) as follows:

  1. Let O be the value of the current object.
  2. If the searchValue is neither undefined nor null, let replacer = GetMethod(searchValue, @@replace).
  3. If replacer is not undefined, return searchValue[@@replace]](O, replaceValue).

Note: The above description only shows @@replace’s role in the specification and is not the full String. Prototype. replace algorithm logic

Symbol.search

role

@@search for string.prototype.search, custom search logic

example

const upperCaseSearcher = {
    value: ' '[Symbol.search](target){
        return target.search(this.value.toUpperCase());
    }
}
upperCaseSearcher.value = 'world';
'hello WORLD'.search(upperCaseSearcher);/ / 6

Copy the code

Normative interpretation

@@search is called by string.prototype. search (regexp) as follows:

  1. Let O be the value of the current object.
  2. If regexp is neither undefined nor null, let searcher = GetMethod(regexp, @@search).
  3. If searcher is not undefined, return regexp[@@search]](O).

Note: the above description only shows the role of @@search in the specification, not the whole String. Prototype. search algorithm logic

Symbol.split

role

@@split for string.prototype.split, custom split logic

example

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 "]

Copy the code

Normative interpretation

@@split is called by string.prototype. split (separator, limit) as follows:

  1. Let O be the value of the current object.
  2. If separator is neither undefined nor null, let splitter = GetMethod(separator, @@split).
  3. If splitter is not undefined, return regexp[@@split]](O, limit).

Note: The above description only shows the role of @@split in the specification, not the whole string.prototype. split algorithm logic

Symbol.toStringTag

role

@ @ toStringTag through Object. The prototype. ToString invoked, is used to describe objects.

example

const obj = {
    [Symbol.toStringTag]: 'Hello'
}

Object.prototype.toString.call(obj); // "[object Hello]"

class ValidatorClass {}

Object.prototype.toString.call(new ValidatorClass()); // "[object object]" Default value

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]"
Copy the code

Normative interpretation

@ @ toStringTag was Object. The prototype. ToString invoke rules are as follows:

  1. Let O be the value of the current object.
  2. [object null] [object undefined] [object undefined]
  3. BuiltinTag = Array, String, Arguments, Function, Error, Boolean, Number, Date, RegExp, Object
  4. let tag = O[@@toStringTag];
  5. Determine the tag, if not a string, and assign builtinTag to the tag
  6. Returns “object”, tag, and “] “.

The default value

The new @@toStringTag in Es6 is as follows:

object value
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

role

@@toprimitive is called by the toPrimitive abstract method, mainly for type conversion. Let’s look at some examples:

const obj = {
    [Symbol.toPrimitive](hint){
        if(hint === 'number') {
            return 2;
        }
        return '1'; }}const keyObj = {
    '1': 1
};
console.log(1 - obj);// -1 calls the ToNumber conversion
console.log(1 == obj); // true is called when abstracting the equality algorithm
console.log(obj + 1); // called when the 11 + operator is used
console.log(keyObj[obj]); // Call ToPropertyKey for conversion
console.log(0 < obj); // called when comparing algorithms abstractly

obj[Symbol.toPrimitive] = function(){return '2017-05-31'};
console.log(new Date(obj)); // called when the Date is constructed

obj[Symbol.toPrimitive] = function(){return {}};
console.log(obj + 1);// throw type error

Copy the code

Normative interpretation

As ToPrimitive abstract method is one of the most important abstraction methods at the bottom of Es6, there are many call points, so we first pay attention to its implementation.

ToPrimitive (input [, PreferredType]) is defined as follows:

  1. Check whether the current input is obj, if not, return input directly

  2. Set the type conversion identifier according to the PreferredType and assign it to the hint variable, default

  3. If the PreferredType is Number, the hint is assigned Number, and the PreferredType is String, the hint is assigned String.

  4. Let exoticToPrim = GetMethod(input, @@toprimitive), if exoticToPrim is not undefined

    1. Call input[@@toprimitive](hint) and assign to result
    2. Return result directly if result is not Object, otherwise raise type Error
  5. If hint is default, the value is assigned to number

  6. Call OrdinaryToPrimitive(input, hint)

OrdinaryToPrimitive is a ToPrimitive method defined by the Es5 specification.

  1. First check whether hint is string or number, and raise TypeError if it is neither
  2. If hint is string, try calling toString first and then valueOf
  3. Otherwise, try calling valueOf first and then toString.
  4. If neither method is present, or if both calls return Object, TypeError is raised

Second, let’s look at the ToPrimitive call point:

  • ToNumber(input) If input is Object, try calling ToPrimitive(input, ‘number’)
  • ToString(Input) If input is Object, try calling ToPrimitive(Input, ‘string’)
  • ToPropertyKey(input) tries to call ToPrimitive(input, ‘string’)
  • For abstract comparisons (e.g., a < b), try calling ToPrimitive(input, ‘number’) first
  • The abstract equality operation is (==). ToNumber is called if both sides are Number and String or if one of them is Boolean. Otherwise, if one side is String, Number, or Symbol and the other is Object, ToPrimitive(Object side value)
  • The binary + operator triggers the ToPrimitive, ToString, ToNumber actions
  • When constructing Date, ToPrimitive is triggered for parameters of type other than DateValue
  • ToJSON will trigger ToPrimitive(thisValue, ‘number’)
  • Other, but not limited to, operations that call ToNumber, such as: ++,–,+,- numeric operators, set array length, sort, math.max (min), Number(value), isNaN, etc.
  • The operations that call ToString design aspects of the ES specification are not covered here.

Symbol.species

role

In the ES specification, many methods need to take the constructor of the current caller and construct an object from that constructor. This may sound abstract, but let’s look at the example first.

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
Copy the code

As we can see from the above example, arrays after maps are constructed using MyArray. Sometimes we want to create derived objects using the constructor we specify.

class MyArray extends Array{
    static [Symbol.species] = Array;
    // same as above
    //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
Copy the code

Normative interpretation

In the ES6 specification, the symbol. species extension property is used primarily for two abstract actions, SpeciesConstructor and ArraySpeciesCreate. Let’s first look at how these two abstract actions are performed.

SpeciesConstructor (O, defaultConstructor) is defined as follows: where O is the current caller and defaultConstructor is the defaultConstructor if no @@species attribute is present in O

  1. Let C = o.constructor
  2. Return defaultConstructor if C is undefined.
  3. Raise TypeError if C is not an object
  4. let S = O[@@species]
  5. Return defaultConstructor if S is null or undefined.
  6. Call IsConstructor(S) to determine whether S is a constructor and return S if so.
  7. Throw a TypeError

ArraySpeciesCreate (originalArray, Length) is defined as follows: where originalArray is the current calling array

  1. Let isArray = isArray (originalArray).
  2. If isArray is false, return New Array(length).
  3. let C = originalArray.constructor
  4. If C is the constructor, check whether C is the same as the Array constructor in the current global environment. If not, set C to undefined (to prevent object creation across Windows).
  5. If C is Object
    1. C = C[@@species]
    2. If C is null, reset to undefined
  6. If C is undefined, return new Array(length).
  7. If C is not a constructor, TypeError is raised.
  8. Create an array of length based on C.

Note: This is a simplification of the specification, removing some assertions and judgments

Let’s look at the SpeciesConstructor call point:

  • Call the [symbol.split] method on the re prototype (the [symbol.split] method passed in to the re is called when the split method of the string is called)
  • Triggered when TypedArray is created (which also includes the Slice, Subarray, and Map methods of TypedArray)
  • [Shared] ArrayBuffer. Prototype. Slice is called the trigger
  • Promise.prototype. Then or finally

For example,

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
Copy the code

ArraySpeciesCreate call point: Mainly used when invoking methods on the Array prototype, including concat, filter, flat,map,slice,splice methods

The default value

The @@species default for javascript primitive types defined in the ES6 specification is Return the this value.

Symbol.iterator

role

This is probably the most commonly used for customization, as it allows us to customize iterators, and sets, maps, and other iterations in the ECMAScript specification are implemented based on it.

In Typescript’s Es6 signature library, we see the iterator’s signature as follows:

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

interfaceIteratorYieldResult<TYield> { done? :false;
    value: TYield;
}

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

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

Copy the code

We can see from the signature that implementing a custom iterator requires extending the [symbol. iterator] method, which returns an iterator. The next method in iterator takes a value that returns the IteratorResult. The return method is used if for… The return method is called when the of loop exits prematurely (usually because of an error or a break statement). The throw method, which can throw an error outside a function and then catch it inside a Generator function, is used primarily with the Generator.

Let’s look at two examples to get a feel for it.

function *可迭代 () {
  yield 1;
  yield 2;
  yield 3;
};
// iterable() returns an iterator
for(const val of iterable()){
    console.log(val);
    // output 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 calls the return method
    break;
}

for(const val of array){
    console.log(val); / / 0
    // Return iterator calls the return method
    throw new Error(a); }// // is equivalent to the code above
// 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;
};

// Extend the default call iterator
console.log([...myIterable]); / / [1, 2, 3]

function *iterable2 () {
  yield* myIterable; // Hover myIterable iterator
};

for(const val of iterable2()){
    console.log(val); / / 1, 2, 3
}

function consoleArgs(. args){
    console.log(args); } consoleArgs(... myIterable);// The remaining arguments call the default call iterator

Copy the code

Normative interpretation

To clarify the @@iterator call point:

  • When the abstract GetIterator (obj [, hint [, method]]) is called, hint can be async or sync. The default value is sync. Method is the specified method that returns an iterator
  • Call abstract methods CreateUnmappedArgumentsObject and CreateMappedArgumentsObject (here is mainly dealing with the arguments calls)
  • Call Array. When the from
  • Call % % TypedArray. When the from

One by one, we will analyze the concrete implementation and its role

  1. GetIterator (obj [, hint [, method]]) is defined as follows
    1. If hint is undefined, reset to sync
    2. If method is not provided, perform the following operations
      1. If hint is async
        1. method = GetMethod(obj, @@asyncIterator).
        2. If method is undefined
          1. let syncMethod = GetMethod(obj, @@iterator)
          2. let syncIteratorRecord = GetIterator(obj, sync, syncMethod)
          3. Return CreateAsyncFromSyncIterator (syncIteratorRecord). / / CreateAsyncFromSyncIterator to abstract method, is used to create asynchronous Iterator through the Iterator.
      2. method = GetMethod(obj, @@iterator)
    3. let iterator = obj.method();
    4. Raises TypeError if iterator is not an Object
    5. let nextMethod = iterator.next;
    6. let iteratorRecord = { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }
    7. return iteratorRecord;

As we can see from the above algorithm, GetIterator ultimately returns a wrapped iterator object. So where are the GetIterator abstract methods called?

  • Let array = [1, 2…array2];
  • Let [one] = array;
  • When processing rest arguments, function gen(… args){}; gen(… array);
  • Function gen([one]){}; gen(array);
  • Yield * when called, functiongen() { yield array };
  • Array.from when called array. from(Array).
  • New Set, new Map when called (including WeakSet and WeakMap), new Set(array).
  • Promise. All | race called, Promise. All (array).
  • When “for of” is called.

Iterators may need a separate document because of the number of invocation points involved, but here’s a look at the specification for of:

The for of execution consists of two main parts:

  1. Call the ForIn/OfHeadEvaluation abstract method to return the iterator
  2. Call ForIn/OfBodyEvaluation to execute the iterator

ForIn/OfHeadEvaluation (TDZnames, expr, iterationKind) The name of the bound environment variable, the statement after “of”, and the type of iteration (including enumerate, async-iterate, iterate). What it means and what it does let’s move on.

  1. Set oldEnv to the current execution environment
  2. If TDZnames is not empty, perform the following operations
    1. TDZ is a new declarative environment created using oldEnv
    2. TDZEnvRec is set to the TDZ environment record entry
    3. Bind TDZnames to TDZEnvRec
    4. Set the lexical environment for the current execution context to TDZ
  3. Example Set exprValue to the value of expr
  4. Check whether iterationKind is enumerate. If so, enumerate is used for in.
    1. If exprValue is null or undefined, return Completion{[[Type]]: break, [[Value]]: empty, [[Target]]: Empty} (this is a type in the ES specification that controls break, continue, return, and throw, which can be seen here as breaking out of a loop)
    2. let obj = ToObject(exprValue)
    3. Return EnumerateObjectProperties (obj) / / EnumerateObjectProperties for circular object, returns an iterator object, is not discussed here
  5. Otherwise,
    1. Checks whether iterationKind is async-iterate, or sets iteratorHint to async
    2. Otherwise, iteratorHint is sync
    3. Call GetIterator(exprValue, iteratorHint) to get the iterator and return it

The result returned by the above method is passed to ForIn/OfBodyEvaluation for variable execution ForIn/OfBodyEvaluation (LHS, STMT, iteratorRecord, iterationKind, lhsKind, The labelSet [, iteratorKind] specification is defined as follows:

There are many parameters, let’s explain them one by one:

  • LHS: of before the declaration statement
  • STMT: for of circulatory body
  • IteratorRecord: Iterator returned from above
  • IterationKind: type of iteration (same as above)
  • LhsKind: Assignment, varBinding or lexicalBinding
  • LabelSet: control statements (e.g. Return, break, continue)
  • IteratorKind: iterator type (used to identify async)

The algorithm execution logic is as follows:

  1. If iteratorKind is empty, set it to sync
  2. The lexical environment of the current execution context is represented by the oldEnv variable
  3. Declare a V variable, set to undefined
  4. If LHS is a destruct statement, the destruct statement is processed
  5. Start to cycle
    1. let nextResult = iteratorRecord.[[Iterator]][iteratorRecord.[[NextMethod]]]();
    2. If iteratorKind is async, nextResult = Await(nextResult) (asynchronous iterator, hovering with Await)
    3. IteratorComplete(nextResult) to determine if the iteration is complete.
    4. If done is true, return NormalCompletion(V).
    5. Let nextValue = IteratorValue(nextResult)
    6. Here we mainly parse LHS according to lhsKind to obtain the corresponding variable binding reference (specification description is too detailed, we first understand its role here)
    7. If status is not NormalCompletion(for example, if there is an exception), then iterationKind is judged. If iterationKind is enumerate, status is returned. IteratorRecord.[[Iterator]]iteratorRecord.[[ReturnMethod]]
    8. Set result to the result of executing STMT (Result is also a Completion)
    9. Check whether the result can continue the loop (break, return, etc.). If not, check whether iterationKind is iterationKind. If iterationKind is enumerate, return status. IteratorRecord.[[Iterator]][[iteratorRecord[[ReturnMethod]]]()
    10. If result.[[Value]] is null, V = result.[[Value]]

The above algorithm removes some of the cumbersome steps in the specification, especially the binding parsing part of LHS. For more information, check out the ECMAScript specification documentation.

The default value

Most of Es6’s built-in objects implement iterators, as follows:

  • String.prototype [ @@iterator ]
  • Array.prototype [ @@iterator ]
  • %TypedArray%.prototype [ @@iterator ]
  • Map.prototype [ @@iterator ]
  • Set.prototype [ @@iterator ]
  • %IteratorPrototype% [ @@iterator ]

Symbol.asyncIterator(@@asyncIterator)

role

Symbol.asyncIterator specifies the default asynchronous iterator for an object. If an object has this property set, it is an asynchronous iterable and can be used for await… Of circulation.

Let’s look at some examples:

  • Example 1:
const myAsyncIterable = new Object(a); myAsyncIterable[Symbol.asyncIterator] = async function* () {
    yield 1;
    yield 2;
    yield 3;
};

(async() = > {for await (const x of myAsyncIterable) {
        console.log(x);
        / / output:
        / / 1
        / / 2
        / / 3
    }
})();
Copy the code

You can also iterate through promises through it

  • Example 2:
const myAsyncIterable = new Object(a);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);
        / / output:
        / / 1
        / / 2
    }
})();
Copy the code

You can also customize asynchronous iterators

  • Example 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);
        / / output:
        / / 1
        / / 2
    }
})();
Copy the code

Normative interpretation

@@asynciterator and @@iterator are treated in the same way in the specification definition, except that the ForIn/OfBodyEvaluation iteratorKind parameter is set to async. @@asynciterator is processed with Await action when executing a function.

Symbol.unscopables(@@unscopables)

role

Object’s symbol. unscopables property pointing to an object. This object specifies which attributes are excluded from the with environment when the with keyword is used

const object1 = {
  property1: 42
};

object1[Symbol.unscopables] = {
  property1: true
};

with (object1) {
  console.log(property1);
  // expected output: Error: property1 is not defined
}
Copy the code

Normative interpretation

@@unscopables is used for HasBinding calls

HasBinding checks whether the object is bound to the current environment entry. The HasBinding in the specification is filtered through @@unscopables.

The default value

Array.prototype specifies @@unscopables as follows:

{
    "copyWithin":true."entries":true."fill":true."find":true."findIndex":true."flat":true."flatMap":true."includes":true."keys":true."values":true
}
Copy the code