May 08, 2021 ES6
Specification files are the official standards of computer language, detailing grammar rules and implementation methods.
In general, there is no need to read the specifications unless you are writing the compiler. B ecause the specifications are very abstract and refined, and lack of examples, not easy to understand, and to solve practical application problems, not much help. H owever, if you have a difficult grammatical problem and can't find the answer, you can check the specification file to find out what the language standard says. Specifications are the "last move" to solve the problem.
This is necessary for the JavaScript language. B ecause of its complex use of the scene, grammar rules are not uniform, many exceptions, the behavior of various operating environments is inconsistent, resulting in strange grammar problems, any grammar book can not cover all situations. Looking at the specifications is the most reliable and authoritative way to solve grammatical problems.
This chapter describes how to read ecMAScript 6 specification files.
EcMAScript 6 specifications are available for free download and online reading on the www.ecma-international.org/ecma-262/6.0/ of the ECMA International Standards Organization (ECMA).
This spec file is quite large, with 26 chapters and 545 pages of A4 print. I t is characterized by very detailed regulations, each grammatical behavior, the implementation of each function has been described in detail and clearly. B asically, compiler authors simply translate each step into code. This largely ensures consistent behavior for all ES6 implementations.
Chapters 1 through 3 of the 26 chapters of ecMAScript 6 specifications are an introduction to the document itself and have little to do with language. C hapter 4 is a description of the overall design of the language that interested readers can read. C hapters 5 through 8 are descriptions of the macro level of language. C hapter 5 is an introduction to noun interpretation and writing of specifications, Chapter 6 describes data types, Chapter 7 describes abstractions used within languages, and Chapter 8 describes how code runs. Chapters 9 through 26 describe the specific syntax.
For the average user, with the exception of Chapter 4, all other chapters deal with the details of an aspect, without having to read through, as long as the relevant chapters can be consulted when used.
ES6 specifications use specialized terms that can help you read them. This section describes a few of them.
The
“抽象操作”
are internal methods of the engine that cannot be called externally.
Specifications define a series of abstract operations, define their behavior, and leave them to the various engines themselves.
For example,
Boolean(value)
algorithm is this.
Let b be ToBoolean(value) .
ToBoolean
an abstract operation, an algorithm for the inside of the engine to find Boolean values.
Many function algorithms use the same steps multiple times, so the ES6 specification pulls them out and defines them as "abstract operations" for easy description.
ES6
specification
键值对
data structure of key-value pairs
Record
where each set of key value pairs is called
field
This means that a Record consists of multiple fields, each containing a key name and a value.
ES6
specifications use a lot of this writing method, such as the
[[Notation]]
the "Writable", the "Get", the "Set", and so on.
It is used to refer to
field
name of the field.
For example, obj is a Record that has a Prototype property. E S6 specifications do not write obj. P rototype, but write obj. [ [Prototype]] 。 In general, the properties of writing using the method of writing are the internal properties of an object.
All JavaScript functions have an internal property, call, to run the function.
F.[[Call]](V, argumentsList)
In the above code, F is a function object, the internal method of the function, F. ( call ) ( ) ( ) means to run the function, V is the value of the this at the time of the runtime of the call, and argumentsList is an argument to the function that was passed in at the time of the call.
Each statement returns a Process Record that represents the result of the run.
Each Final Record has a
[[Type]]
the type of run result.
There are five possible values for the .
If
[[Type]]
is
normal
called normalization, which means that it is working properly. T
he other values are called abrupts.
Among other things, the developer only needs to look at the situation where the
[[Type]]
throw
i.e. the run is wrong; the three values of
break
continue
return
all appear only in a specific scenario, without considering it.
The running process of abstract operations, generally as follows.
Let result be AbstractOp() .
If result is an abrupt completion, return result .
Set result to result.[[Value]] .
return result .
The first step above calls the abstract operation AbstractOp() to get result, which is a Complete Record. T he second step, if the result belongs to the abrupt finish, returns directly. I f not returned here, the result belongs to normality. T he third step is to set the value of result to resultCompletionRecord. [ [Value]] 。 In the fourth step, return to result.
ES6 specifications express this standard process in a short, short-form manner.
Let result be AbstractOp() .
ReturnIfAbrupt(result) .
return result .
ReturnIfAbrupt (result) in this short form represents the second and third steps above, i.e. if there is an error, the error is returned, otherwise the value is taken out.
There are even further short forms.
Let result be ? AbstractOp() .
return result .
The above process? , on behalf of AbstractOp() may report errors. Once an error is reported, the error is returned or the value is taken out.
Apart from? The ES 6 specification also uses another short-form symbol! .
Let result be ! AbstractOp() .
return result .
The above process! , which represents that AbstractOp() does not report an error and must return a normal complete, which always takes out the value.
Here are some examples of how to use this specification.
The equal operator
( == )
syntax behaves in a variety of way and is counterintuitive.
This section will see how the specifications describe its behavior.
Please look at this expression below, what is its value.
0 == null
If you're not sure about the answer, or want to know what's going on inside the language, you can go and look at the specifications, section 7.2.12, which describes the equal operator.
The specification describes each grammatical behavior in two parts: first the overall behavior description, then the algorithmic details of the implementation. The overall description of the equal operator is in only one sentence.
“The comparison x == y , where x and y are values, produces true or false .”
The above sentence means that the equal operator is used to compare two values and return true or false.
Here are the details of the algorithm.
ReturnIfAbrupt(x).
ReturnIfAbrupt(y).
If Type(x) is the same as Type(y), then
Return the result of performing Strict Equality Comparison x === y .
If x is null and y is undefined , return true .
If x is undefined and y is null , return true .
If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y) .
If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y .
If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y .
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y) .
If Type(x) is either String, Number, or Symbol and Type(y) is Object, then return the result of the comparison x == ToPrimitive(y) .
If Type(x) is Object and Type(y) is either String, Number, or Symbol, then return the result of the comparison ToPrimitive(x) == y .
Return false .
The above algorithm, with a total of 12 steps, translates below.
如果 x 不是正常值(比如抛出一个错误),中断执行。
如果 y 不是正常值,中断执行。
如果 Type(x) 与 Type(y) 相同,执行严格相等运算 x === y 。
如果 x 是 null , y 是 undefined ,返回 true 。
如果 x 是 undefined , y 是 null ,返回 true 。
如果 Type(x) 是数值, Type(y) 是字符串,返回 x == ToNumber(y) 的结果。
如果 Type(x) 是字符串, Type(y) 是数值,返回 ToNumber(x) == y 的结果。
如果 Type(x) 是布尔值,返回 ToNumber(x) == y 的结果。
如果 Type(y) 是布尔值,返回 x == ToNumber(y) 的结果。
如果 Type(x) 是字符串或数值或 Symbol 值, Type(y) 是对象,返回 x == ToPrimitive(y) 的结果。
如果 Type(x) 是对象, Type(y) 是字符串或数值或 Symbol 值,返回 ToPrimitive(x) == y 的结果。
返回 false 。
Because the type of 0 is numeric, the type of null is Null (this is the specification 4.3.13 subsecond and is the result of an internal Type operation, regardless of the typeof operator). So the first 11 steps above don't get results, and you don't get false until step 12.
0 == null // false
Let's look at another example.
const a1 = [undefined, undefined, undefined];
const a2 = [, , ,];
a1.length // 3
a2.length // 3
a1[0] // undefined
a2[0] // undefined
a1[0] === a2[0] // true
In the code above, the members of array a1 are three undefined and the members of array a2 are three empty bits. The two arrays are similar in length, 3, and the members of each position read out as undefined.
However, they are actually significantly different.
0 in a1 // true
0 in a2 // false
a1.hasOwnProperty(0) // true
a2.hasOwnProperty(0) // false
Object.keys(a1) // ["0", "1", "2"]
Object.keys(a2) // []
a1.map(n => 1) // [1, 1, 1]
a2.map(n => 1) // [, , ,]
The above code lists four operations, with different results for arrays a1 and a2. T he first three operations (the in operator, the hasOwnProperty method for the array, the Object.keys method) all indicate that array a2 does not take the property name. The last operation (the array's map method) indicates that array a2 did not occur traversal.
Why is a1 inconsistent with the behavior of a2 members? What's the difference between an array's members, undefined or empty?
The above specification makes it very clear that the empty space of the array is reflected in the length property, that is, the empty space has its own position, but the value of this position is undefined, i.e. the value does not exist. If you have to read, the result is undefined (because undefined does not exist in the JavaScript language).
This explains why the in operator, the hasOwnProperty method for the array, and the Object.keys method do not take the property name of the empty bit. Because this property name does not exist at all, the specification does not say that the property name (position index) is to be assigned to the empty bit, only that it is to add 1 to the location index of the next element.
As for why the map method for arrays skips empty spaces, see the next section.
Section
22.1.3.15 of the specification defines
the map method
map
array.
This section starts with an overall description of the behavior of the
map
method, with no mention of array empty spaces.
The following algorithm describes this.
Let O be ToObject(this value) .
ReturnIfAbrupt(O) .
Let len be ToLength(Get(O, "length")) .
ReturnIfAbrupt(len) .
If IsCallable(callbackfn) is false , throw a TypeError exception.
If thisArg was supplied, let T be thisArg ; else let T be undefined .
Let A be ArraySpeciesCreate(O, len) .
ReturnIfAbrupt(A) .
Let k be 0.
Repeat, while k < len
Let Pk be ToString(k) .
Let kPresent be HasProperty(O, Pk) .
ReturnIfAbrupt(kPresent) .
If kPresent is true , then
Let kValue be Get(O, Pk) .
ReturnIfAbrupt(kValue) .
Let mappedValue be Call(callbackfn, T, «kValue, k, O») .
ReturnIfAbrupt(mappedValue) .
Let status be CreateDataPropertyOrThrow (A, Pk, mappedValue) .
ReturnIfAbrupt(status) .
Increase k by 1.
Return A .
The translation is as follows.
A closer look at the algorithm above reveals that the previous steps are all right when working with an array of all empty spaces.
When you go to Step 2 in Step 10,
kPresent
error because the property name for the empty space does not exist for the array, so it will be returned and no subsequent steps will be taken.
const arr = [, , ,];
arr.map(n => {
console.log(n);
return 1;
}) // [, , ,]
In the code above,
arr
an array of all empty spaces, and when the map method traverses a member, it finds that it is empty, skips directly and does not enter the callback function.
Therefore, the
console.log
inside the callback function is
map
and the entire map method returns a new array full of empty spaces.
The implementation of the V8 engine for the map method is as follows, and you can see that the algorithm description is exactly the same as the specification.
function ArrayMap(f, receiver) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayMap(f, receiver, array, length);
}
function InnerArrayMap(f, receiver, array, length) {
if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
var accumulator = new InternalArray(length);
var is_array = IS_ARRAY(array);
var stepping = DEBUG_IS_STEPPING(f);
for (var i = 0; i < length; i++) {
if (HAS_INDEX(array, i, is_array)) {
var element = array[i];
// Prepare break slots for debugger step in.
if (stepping) %DebugPrepareStepInIfStepping(f);
accumulator[i] = %_Call(f, receiver, element, i, array);
}
}
var result = new GlobalArray();
%MoveArrayContents(accumulator, result);
return result;
}