This article is a primer for those who want to read the specification but don’t know how to start. In the actual work is actually rarely encountered strange strange JS problems, but in JS test questions often see some strange and strange problems. The best explanation for these problems is the specification document, which explains them clearly, so why go looking for other people’s explanations? If you find the specification too hard to understand, you may not be opening it the right way. Here is the latest specification ecma262

An overview of

The first recommendation is not to read the specification like a book, but rather like a manual. The first step is to understand what is and isn’t part of the specification. Simply put, syntactic semantics and built-in attribute methods are part of the specification, and host-environment related things are not. Next, we take a look at the organization of the specification document. Chapters 1-3 are skimmed over, and chapter 4 provides an overview of the organization of the document in addition to terms and definitions: Chapter 5 defines conventions and conventions used throughout the document; Chapter 6-9 defines the run-time execution environment; Chapters 10-16 define syntactic features and runtime semantics for the actual language; 17-26 Define the standard library; Chapter 27 Memory consistency Model. It can be seen that chapters 5-9 May be needed in reading other chapters, while chapters 10-16 are of practical concern. As mentioned above, it is not recommended to read from top to bottom, but to read to your own needs. I’ll take you through this section with a practical example, and I’ll describe how I found the manual.

Prior to the start

Maybe you can’t wait, but it’s important to do some preparatory work before we start. You are already familiar with the execution environment. If this is not clear, refer back to my previous article on minimal interpreter implementation – explain scopes and closures. Simply put, some of the variables we encounter are just identifiers. We need to maintain an Environment Record, which can be understood as just a table of

. At the same time, this Environment Record can also point to external Environment records. An execution environment corresponds to an environment entry. Every time we enter a function, a new environment is created, and a new environment entry is created.
,>

Language types and specification types are defined in Chapter 6 of the specification. Language types are the types that JS supports. Specification types are the data structures that will be used to explain these specifications, and there are two that you’ll see often enough to explain: Reference and Completion.

Reference

We need to understand the function of the Reference type from the interpreter’s point of view. The variable we see is just an identifier, and we don’t necessarily want its value directly when interpreting executing code. For example, we may assign a value to it. At this point we use an object called the Reference Reference type, which is a simple data structure containing the BaseValue BaseValue and the Reference name ReferencedName. For example, if we see an internal variable name, the resulting reference type ref is parsed, the reference name is the string ‘name’, and the base value is the environment entry in which it resides. Data structures like this allow us to assign or parse a value to a variable.

The identifier is defined in section 12.1 and we’ll start with the section on Static Semantics, starting with SS or Static Semantics, which defines the early parsing error generation. We don’t really care about this unless we have syntax errors. Go straight to RS: Evaluation, which is the topic we’re looking at. Runtime semantics is how the interpreter interprets and executes the code given the syntax tree.

You’ll see that the interpreted execution of the identifier simply calls a ResolveBinding. The result of evaluating an IdentifierReference is always a value of type Reference. It is emphasized that it must return a reference type. If you click ResolveBinding again, it simply calls the GetIdentifierReference internal method with the current execution environment as an argument. The GetIdentifierReference method recursively searches the external environment record entry until the identifier is found in the outermost layer or in an execution environment. The reference base value returned is the environment entry or undefined.

For interpreted execution of member attribute access like user.name or user.displayName, the resulting reference base value can you try to find the following for yourself? 12.3.2 Property Accessors,

Completion

The Completion type is used to describe some state control, and when we interpret the execution of a statement, we don’t just get a value, we need a data structure that describes the type of statement, such as whether it terminates, breaks out of a loop, or returns a type. Completion is structured like {Type, Value, Target}. The Type may be normal, break, continue, return, and throw.

Returnifabnormal is an examination of the returned result, returning Value if Type is Normal, otherwise returning the end Type.

In addition, many algorithms are abbreviated when describing the return end type.

  1. Return "Infinity".meanReturn NormalCompletion("Infinity").
  2. ? OperationName().saidReturnIfAbrupt(OperationName()).
  3. ! OperationName().Asserts that the completion type is Normal

It is recommended that you start reading without considering the end type, and that you do not consider the transfer of control without affecting your understanding of the specification. It is explained here to reduce unnecessary confusion when reading the specification.

It is difficult to find a suitable word to translate these terms. The translation of this article uses the Chinese version of Yan Haijing’s standard Terminology translation yanhaijing.com/es5/ there is no subdivision of execution environment -> environment record items (lexical environment, variable environment).

practice

1. Continuous assignment

Let’s start with an easy one. Here’s an assignment problem.

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x) // undefined
console.log(b.x) // {n:2}
Copy the code

A.x = a = {n: 2}; This is an assignment problem. If you happen to get it wrong, you can look it up in the specification. Search assignment in the specification to find the section on assignment operations and navigate directly to the RS section:

12.15.4 Assignment Operators – Runtime Semantics Evaluation

The first line is to make sure that the left side is not an object literal or an array literal, since [a, b] = [b, a] is now supported, too. We want to interpret the execution of a.x (also the member attribute mentioned above), resulting in a reference type of the form {BaseValue: a, ReferencedName: ‘x’}. So let’s just look at the normal case, D, and explain how to perform the expression on the right hand side to get to rval. Finally, the assignment is performed and rval is returned.

PutValue and GetValue can also be understood from the noun, but there is no more explanation here.

2. Bool and non-strict comparison

First look at this strange equation [] ==! []

To explain this, let’s explain it first! Operator, this is the unary operator, goes directly to the semantics part of the runtime

  1. Let expr be the result of evaluating UnaryExpression.
  2. Let oldValue be ! ToBoolean(? GetValue(expr)).
  3. If oldValue is true, return false.
  4. Return true.

The first step is to interpret the expression after executing the operator. The second step is to get the value of the reference, which is the empty array, and then call ToBoolean. ToBoolean is defined in Section 7.1.2. This is the conversion table:

It is also worth pointing out that the if statement is also evaluated by calling ToBoolean. As to why [] ==! [] Is true, we need to look at the definition of == and find 12.11.3 RS:Evaulation of Equality Expression, which is judged according to 7.2.15 Abstract Equality Comparison. This section has many algorithm steps, but we only need to find our corresponding entry. Now we know that the value on the right is false, which is equivalent to comparing [] == false, and we can find that it corresponds to article 9

  1. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).

Then we are equivalent to comparing [] == 0, which in turn corresponds to article 11.

  1. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result of the comparison ? ToPrimitive(x) == y.

This ToPrimitive method converts an Object to a primitive type. This method simply adds a hint and calls the OrdinaryToPrimitive(input, hint) method. The hint is usually a Number, but you can override it. In the specification, only Date and Symbol override the default values. Date is a String.

Then look at the OrdinaryToPrimitive method, which calls toString and valueOf of the object to find the primitive type that is not the object. The order depends on the hint we pass. Here [] calls toString (because valueOf still returns the object type) to return the empty string ”. Then compare ” == 0 to true.

3. Implicit type conversion

‘5’ – ‘2’ and ‘5’ + ‘2’

Expression of related specifications in chapter 12, add and subtract operations defined in section 12.8 You can see only one, the binary operation is called EvaluateStringOrNumericBinaryExpression operator parameters of this function is introduced to different.

  1. Return ? EvaluateStringOrNumericBinaryExpression(AdditiveExpression, -, MultiplicativeExpression).

Then check the function specification defines the Runtime Semantics: EvaluateStringOrNumericBinaryExpression (leftOperand opText, rightOperand)

  1. Let lref be the result of evaluating leftOperand.
  2. Let lval be ? GetValue(lref).
  3. Let rref be the result of evaluating rightOperand.
  4. Let rval be ? GetValue(rref).
  5. Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).

Both side is the same interpretation, and alignment is evaluated, and then call ApplyStringOrNumericBinaryOperator, then see the definition of ApplyStringOrNumericBinaryOperator:

12.15.5 Runtime Semantics: ApplyStringOrNumericBinaryOperator

Returns string concatenation if there are strings to the left or right.

4. this

If this is too simple, try reading the this section to define that the this keyword simply calls ResolveThisBinding(). Then look at 8.3.4 ResolveThisBinding ():

  1. Let envRec be GetThisEnvironment().
  2. Return ? envRec.GetThisBinding().

8.3.3 GetThisEnvironment()

  1. Let env be the running execution context’s LexicalEnvironment.
  2. Repeat,

    a. Let exists be env.HasThisBinding(). b. If exists is true, return env.

    c. Let outer be env.[[OuterEnv]].

    d. Assert: outer is not null.

    e. Set env to outer.

The whole process is to check the execution environment for this bindings, and if not, look externally until one is found. We know that the execution environment created by the arrow function does not have this binding. For details on when a new execution environment is created or what is done during a Function call, see 12.3.6 Function Calls, which is a bit long. It is recommended to consider only the simplest case first, such as user.dispalyName(). RS function calls 12.3.6.1 RS function calls

  1. Let ref be the result of evaluating CallExpression.
  2. Let func be ? GetValue(ref).
  3. Let thisCall be this CallExpression.
  4. Let tailCall be IsInTailPosition(thisCall).
  5. Return ? EvaluateCall(func, ref, Arguments, tailCall).

The first step is to explain how to execute the expression on the left, where you get a reference type. Remember the result of parsing user.displayName. The second step assigns the referenced value to func, leaving tail recursion out of the equation, and then looking at the EvaluateCall definition.

Sometimes the expression on the left is not a reference. For example, the common (0, xx.fn)() comma expression returns the value of the reference. Anyway, the ref base value in a is an object, so perform step I. So in this case GetThisValue(ref) is GetBase(ref), which is user.

Function calls are quite logical, but I won’t go into detail here. Finally, you can see in 9.2.1 [[Call]] how to create a new execution environment, bind this, etc.

other

Variable binding process; Arguments difference between strict mode and non-strict mode let const

conclusion

In general, the specification is the equivalent of a pseudo-code comment, which is actually relatively easy to understand, and it is not difficult to make use of the manual. The purpose of this article is to familiarize you with the manual based on actual scenarios, so you can read only the parts that interest you. Here’s an old problem to test. JavaScript Puzzlers!