Specification files for ES6


May 08, 2021 14:00 ES6


Table of contents


1. Overview

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.

Terminology

ES6 specifications use specialized terms that can help you read them. This section describes a few of them.

Abstract operations

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.

  1. 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.

Record and field

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.

[[Notation]]

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.

  1. 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.

Completion Record

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 .

  • normal
  • return
  • throw
  • break
  • continue

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.

3. Standard processes for abstract operations

The running process of abstract operations, generally as follows.

  1. Let result be AbstractOp() .
  2. If result is an abrupt completion, return result .
  3. Set result to result.[[Value]] .
  4. 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.

  1. Let result be AbstractOp() .
  2. ReturnIfAbrupt(result) .
  3. 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.

  1. Let result be ? AbstractOp() .
  2. 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! .

  1. Let result be ! AbstractOp() .
  2. 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.

4. Equal operator

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.

  1. 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.

  1. 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.

  1. ReturnIfAbrupt(x).
  2. ReturnIfAbrupt(y).
  3. If Type(x) is the same as Type(y), then
  4. Return the result of performing Strict Equality Comparison x === y .
  5. If x is null and y is undefined , return true .
  6. If x is undefined and y is null , return true .
  7. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y) .
  8. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y .
  9. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y .
  10. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y) .
  11. If Type(x) is either String, Number, or Symbol and Type(y) is Object, then return the result of the comparison x == ToPrimitive(y) .
  12. If Type(x) is Object and Type(y) is either String, Number, or Symbol, then return the result of the comparison ToPrimitive(x) == y .
  13. Return false .

The above algorithm, with a total of 12 steps, translates below.

  1. 如果 x 不是正常值(比如抛出一个错误),中断执行。
  2. 如果 y 不是正常值,中断执行。
  3. 如果 Type(x) Type(y) 相同,执行严格相等运算 x === y
  4. 如果 x null y undefined ,返回 true
  5. 如果 x undefined y null ,返回 true
  6. 如果 Type(x) 是数值, Type(y) 是字符串,返回 x == ToNumber(y) 的结果。
  7. 如果 Type(x) 是字符串, Type(y) 是数值,返回 ToNumber(x) == y 的结果。
  8. 如果 Type(x) 是布尔值,返回 ToNumber(x) == y 的结果。
  9. 如果 Type(y) 是布尔值,返回 x == ToNumber(y) 的结果。
  10. 如果 Type(x) 是字符串或数值或 Symbol 值, Type(y) 是对象,返回 x == ToPrimitive(y) 的结果。
  11. 如果 Type(x) 是对象, Type(y) 是字符串或数值或 Symbol 值,返回 ToPrimitive(x) == y 的结果。
  12. 返回 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.

  1. 0 == null // false

5. Empty spaces for the array

Let's look at another example.

  1. const a1 = [undefined, undefined, undefined];
  2. const a2 = [, , ,];
  3. a1.length // 3
  4. a2.length // 3
  5. a1[0] // undefined
  6. a2[0] // undefined
  7. 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.

  1. 0 in a1 // true
  2. 0 in a2 // false
  3. a1.hasOwnProperty(0) // true
  4. a2.hasOwnProperty(0) // false
  5. Object.keys(a1) // ["0", "1", "2"]
  6. Object.keys(a2) // []
  7. a1.map(n => 1) // [1, 1, 1]
  8. 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.

6. The map method for the array

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.

  1. Let O be ToObject(this value) .
  2. ReturnIfAbrupt(O) .
  3. Let len be ToLength(Get(O, "length")) .
  4. ReturnIfAbrupt(len) .
  5. If IsCallable(callbackfn) is false , throw a TypeError exception.
  6. If thisArg was supplied, let T be thisArg ; else let T be undefined .
  7. Let A be ArraySpeciesCreate(O, len) .
  8. ReturnIfAbrupt(A) .
  9. Let k be 0.
  10. Repeat, while k < len
  11. Let Pk be ToString(k) .
  12. Let kPresent be HasProperty(O, Pk) .
  13. ReturnIfAbrupt(kPresent) .
  14. If kPresent is true , then
  15. Let kValue be Get(O, Pk) .
  16. ReturnIfAbrupt(kValue) .
  17. Let mappedValue be Call(callbackfn, T, «kValue, k, O») .
  18. ReturnIfAbrupt(mappedValue) .
  19. Let status be CreateDataPropertyOrThrow (A, Pk, mappedValue) .
  20. ReturnIfAbrupt(status) .
  21. Increase k by 1.
  22. Return A .

The translation is as follows.

  1. Gets the this object of the current array
  2. Return if an error is reported
  3. Find the length property of the current array
  4. Return if an error is reported
  5. If the parameter callbackfn of the map method is not executable, an error is reported
  6. If this is specified in the parameters of the map method, let T equal the parameter, otherwise T is undefined
  7. A new array A is generated, consistent with the length property of the current array
  8. Return if an error is reported
  9. Set k to equal 0
  10. Repeat the following steps as long as k is less than the length property of the current array
    1. Set Pk equals ToString(k) and turns K into a string
    2. Set kPresent equal to HasProperty (O, Pk), i.e. find out if the current array has specified properties
    3. Return if an error is reported
    4. If kPresent equals true, follow these steps
      1. Set kValue equal to Get (O, Pk) and remove the specified properties of the current array
      2. Return if an error is reported
      3. Set mappedValue equal to Call (callbackfn, T, skValue, k, O) to execute the callback function
      4. Return if an error is reported
      5. Set status equal to CreateDataPropertyOrThrow (A, Pk, mappedValue) and place the value of the callback function in the specified position of array A
      6. Return if an error is reported
    5. k Increase by 1
  11. Return A

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.

  1. const arr = [, , ,];
  2. arr.map(n => {
  3. console.log(n);
  4. return 1;
  5. }) // [, , ,]

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.

  1. function ArrayMap(f, receiver) {
  2. CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
  3. // Pull out the length so that modifications to the length in the
  4. // loop will not affect the looping and side effects are visible.
  5. var array = TO_OBJECT(this);
  6. var length = TO_LENGTH_OR_UINT32(array.length);
  7. return InnerArrayMap(f, receiver, array, length);
  8. }
  9. function InnerArrayMap(f, receiver, array, length) {
  10. if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
  11. var accumulator = new InternalArray(length);
  12. var is_array = IS_ARRAY(array);
  13. var stepping = DEBUG_IS_STEPPING(f);
  14. for (var i = 0; i < length; i++) {
  15. if (HAS_INDEX(array, i, is_array)) {
  16. var element = array[i];
  17. // Prepare break slots for debugger step in.
  18. if (stepping) %DebugPrepareStepInIfStepping(f);
  19. accumulator[i] = %_Call(f, receiver, element, i, array);
  20. }
  21. }
  22. var result = new GlobalArray();
  23. %MoveArrayContents(accumulator, result);
  24. return result;
  25. }