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:
-
Determines whether target is an object, and if not raises a TypeError Exception.
-
Let instOfHandler = GetMethod(target, @@hasInstance). // GetMethod is an internal abstract method
-
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
-
Check if the object is IsCallable (see if it is an instance of Function), and if it does not throw a TypeError Exception.
-
Here comes the Es5 specification for Instanceof, which in Es6 is called OrdinaryHasInstance.
Then let’s look at how OrdinaryHasInstance is specified:
-
Check whether the target is IsCallable. If the algorithm above is used, it must be Callable.
-
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
-
Check whether V is Object, if not return false.
-
let P = target.prototype;
-
Check whether P is Object, if not raise TypeError Exception;
-
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:
- Check whether O is an object, if not return false.
- let spreadable = O[@@isConcatSpreadable].
- If spreadable is not undefined, convert it to Boolean and return it.
- 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:
- Generates a new array based on the current called object type, length is 0,
- Loop over the currently called object and the arguments list passed in
- Call IsConcatSpreadable to determine whether it is currently spreadable. If so, perform the following operations
- 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:
- Argument is not Object, return false.
- let matcher = argument[@@match]
- If matcher is not undefined, convert matcher to Boolean and return.
- Return true if argument has a built-in [[RegExpMatcher]] property
- 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:
- Let O be the value of the current object.
- If regexp is neither undefined nor null, let matcher = GetMethod(regexp, @@match).
- 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:
- Let O be the value of the current object.
- If the searchValue is neither undefined nor null, let replacer = GetMethod(searchValue, @@replace).
- 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:
- Let O be the value of the current object.
- If regexp is neither undefined nor null, let searcher = GetMethod(regexp, @@search).
- 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:
- Let O be the value of the current object.
- If separator is neither undefined nor null, let splitter = GetMethod(separator, @@split).
- 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:
- Let O be the value of the current object.
- [object null] [object undefined] [object undefined]
- BuiltinTag = Array, String, Arguments, Function, Error, Boolean, Number, Date, RegExp, Object
- let tag = O[@@toStringTag];
- Determine the tag, if not a string, and assign builtinTag to the tag
- 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:
-
Check whether the current input is obj, if not, return input directly
-
Set the type conversion identifier according to the PreferredType and assign it to the hint variable, default
-
If the PreferredType is Number, the hint is assigned Number, and the PreferredType is String, the hint is assigned String.
-
Let exoticToPrim = GetMethod(input, @@toprimitive), if exoticToPrim is not undefined
- Call input[@@toprimitive](hint) and assign to result
- Return result directly if result is not Object, otherwise raise type Error
-
If hint is default, the value is assigned to number
-
Call OrdinaryToPrimitive(input, hint)
OrdinaryToPrimitive is a ToPrimitive method defined by the Es5 specification.
- First check whether hint is string or number, and raise TypeError if it is neither
- If hint is string, try calling toString first and then valueOf
- Otherwise, try calling valueOf first and then toString.
- 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
- Let C = o.constructor
- Return defaultConstructor if C is undefined.
- Raise TypeError if C is not an object
- let S = O[@@species]
- Return defaultConstructor if S is null or undefined.
- Call IsConstructor(S) to determine whether S is a constructor and return S if so.
- Throw a TypeError
ArraySpeciesCreate (originalArray, Length) is defined as follows: where originalArray is the current calling array
- Let isArray = isArray (originalArray).
- If isArray is false, return New Array(length).
- let C = originalArray.constructor
- 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).
- If C is Object
- C = C[@@species]
- If C is null, reset to undefined
- If C is undefined, return new Array(length).
- If C is not a constructor, TypeError is raised.
- 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
- GetIterator (obj [, hint [, method]]) is defined as follows
- If hint is undefined, reset to sync
- If method is not provided, perform the following operations
- If hint is async
- method = GetMethod(obj, @@asyncIterator).
- If method is undefined
- let syncMethod = GetMethod(obj, @@iterator)
- let syncIteratorRecord = GetIterator(obj, sync, syncMethod)
- Return CreateAsyncFromSyncIterator (syncIteratorRecord). / / CreateAsyncFromSyncIterator to abstract method, is used to create asynchronous Iterator through the Iterator.
- method = GetMethod(obj, @@iterator)
- If hint is async
- let iterator = obj.method();
- Raises TypeError if iterator is not an Object
- let nextMethod = iterator.next;
- let iteratorRecord = { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }
- 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:
- Call the ForIn/OfHeadEvaluation abstract method to return the iterator
- 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.
- Set oldEnv to the current execution environment
- If TDZnames is not empty, perform the following operations
- TDZ is a new declarative environment created using oldEnv
- TDZEnvRec is set to the TDZ environment record entry
- Bind TDZnames to TDZEnvRec
- Set the lexical environment for the current execution context to TDZ
- Example Set exprValue to the value of expr
- Check whether iterationKind is enumerate. If so, enumerate is used for in.
- 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)
- let obj = ToObject(exprValue)
- Return EnumerateObjectProperties (obj) / / EnumerateObjectProperties for circular object, returns an iterator object, is not discussed here
- Otherwise,
- Checks whether iterationKind is async-iterate, or sets iteratorHint to async
- Otherwise, iteratorHint is sync
- 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:
- If iteratorKind is empty, set it to sync
- The lexical environment of the current execution context is represented by the oldEnv variable
- Declare a V variable, set to undefined
- If LHS is a destruct statement, the destruct statement is processed
- Start to cycle
- let nextResult = iteratorRecord.[[Iterator]][iteratorRecord.[[NextMethod]]]();
- If iteratorKind is async, nextResult = Await(nextResult) (asynchronous iterator, hovering with Await)
- IteratorComplete(nextResult) to determine if the iteration is complete.
- If done is true, return NormalCompletion(V).
- Let nextValue = IteratorValue(nextResult)
- 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)
- 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]]
- Set result to the result of executing STMT (Result is also a Completion)
- 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]]]()
- 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