This article is built on the basis of v8 engine, the use of the analysis tool is D8. V8 is Google’s JS engine, and chrome and most browsers use V8 as a JS virtual machine. D8 can understand v8 debugging tools, you can use D8 to view the JS code in the execution process of various intermediate data, such as generated AST, bytecode, binary code and so on.

Js virtual machine V8, there are a number of things to do before this code can be executed. Prepare global execution contexts, including built-in function globals; Prepare global scopes, including global variables, and keep all data in memory during execution. Initialize the stack in memory. Initialize the message system

V8 won’t receive our code until the base environment is ready.

The complete code in the problem looks like this. If you haven’t seen this problem, you can think about what the result of a.x is.

var a = {n:1}
a.x = a = {n:2}
console.log( a.x )
Copy the code

The answer: undefined.

Before we start our analysis, a brief introduction to the v8 interpreter architecture: bytecode, register, stack, heap:

  1. The bytecode is generated by the Ignition interpreter and contains the AST and scope information.
  2. Registers are used to store intermediate data, mainly including general purpose registers R0, R1, etc., which are used to point to the PC register of the next bytecode to be executed, and the stack top register which points to the top of the stack. Accumulator is a special register which is used to store intermediate results.
  3. Stacks and heaps are used to provide instruction sets. The difference is that the instruction sets provided are different.

After receiving the code, V8 first parses and generates the AST using the Parser, and then generates the bytecode using Ignition.

Here is the bytecode generated by V8 for the above code:

0000033A0824FAD6 @    0 : 12 00             LdaConstant [0]
0000033A0824FAD8 @    2 : 26 fa             Star r1
0000033A0824FADA @    4 : 27 fe f9          Mov <closure>, r2
0000033A0824FADD @    7 : 61 37 01 fa 02    CallRuntime [DeclareGlobals], r1-r2
0000033A0824FAE2 @   12 : 7d 01 00 29       CreateObjectLiteral [1], [0], #41
0000033A0824FAE6 @   16 : 15 02 01          StaGlobal [2], [1]
0000033A0824FAE9 @   19 : 13 02 03          LdaGlobal [2], [3]
0000033A0824FAEC @   22 : 26 fa             Star r1
0000033A0824FAEE @   24 : 7d 03 05 29       CreateObjectLiteral [3], [5], #41
0000033A0824FAF2 @   28 : 15 02 01          StaGlobal [2], [1]
0000033A0824FAF5 @   31 : 26 f9             Star r2
0000033A0824FAF7 @   33 : 2d fa 04 06       StaNamedProperty r1, [4], [6]
0000033A0824FAFB @   37 : 27 f9 fb          Mov r2, r0
0000033A0824FAFE @   40 : 25 fb             Ldar r0
0000033A0824FB00 @   42 : aa                Return
Copy the code

Next, let’s start this article.

One, from the firstvar a = { n : 1 }start

If var a declares a variable with identifier A, the basic concepts are lost. Pre-parsing will process this code into an AST tree that looks something like this.

Var a = {n: 1} = {n: 1} = {n: 1} = {n: 1} = {n: 1} = {n: 1} = {n: 1} Var is never evaluated.

000002110824FA82@ 0:12 00 LdaConstant [0] // Write the contents of constant pool [0] into accumulator // Accumulator: A 000002110824FA84@ 2: 26 fa Star r1 // save the contents of accumulator in register R1 // undefined, register R1: a 000002110824fa86@4: 27 fe f9 Mov <closure>, R2 // use undefined, r1 // use undefined, R2 // use undefined 61 37 01 fa 02 CallRuntime [DeclareGlobals], r1-r2 // Call CallRuntime [DeclareGlobals], r1-r2 and declare a as a global variable. // The global stack is [a: undefined].Copy the code

And then {n: 1}

CreateObjectLiteral [1], [0], #41 // Create a literal object that is allocated in the heap, write the address of the reference to the object with array subscript, and write the address to the accumulator. // accumulator: {n:1}, r1: undefined, R2: aCopy the code

And then we do the assignment of =

000000550824fae6@ 16:15 02 01 StaGlobal [2], [1] // Store the contents of the accumulator in the global variable A. [2] = (a: {n: 1})Copy the code

2. Thena.x = a = { n : 2 }

Var a = {n: 1}

The key is the following a.x = a = {n: 2}.

For example, var x = y = 1, this is a typical assignment statement. The syntax is to bind the value of expression 1 to y, and the value of y to x, instead of x being equal to the value of y being equal to 1, only y = 1 is an expression. Y is just an accident. In JS, if you assign to a variable that doesn’t exist, js creates that variable in a global variable.

Assignment to constant variable (y = 2) {Assignment to constant variable (y = 2);} This statement, we can interpret as y = 1; const x = y

X = a = {n: 2}; x = a = {n: 2}; A.x = a.

At this point, some people may wonder why the result of a.x is undefined instead of A.

In JS, any operation is evaluated strictly from left to right, and a.x is processed first. This is an operation indicating that a.x represents {n: 1} object memory address, the calculation result of a.x is fixed as a reference, let’s assume that a.x first obtains the memory address of a is 100000f90. A = {n: 2} creates a new object in the heap space with the memory address of 100000f98.

A = assigns a value of 100000f98 to a. This does not affect the value of a.x, because a.x is already a result of calculation, not a variable. That is, a.x operates on {n: 1} in heap memory instead of {n: 2}.

After that, bind the memory address 100000f98 to the x property of the object pointed to by 100000f90.

Var a = {n: 1} var a = {n: 1} var a = {n :2} var a = {n :2} var a = {n: 1} var a = {n :2} var a = {n: 1} var a = {n :2} var a = {n :2} 2}, ref = {n: 1, x: {n: 3}}Copy the code

Now that you understand this, you can take a look at what bytecode V8 generates and how it is handled.

Read {n: 1} and store it in register R1

000000550824FAe9@ 19:13 02 03 LdaGlobal [2], [3] // Read the contents of the global variable [2] ({n: 1} address) and store it in the accumulator 000000550824FAec@ 22: 26 fa Star R1 // Accumulator: {n: 1}, R1: {n: 1}Copy the code

Create {n: 2}, change the address of A in the global variable stack to the object, and store the object in register R2.

000000550824Fae@24:7d 03 05 29 CreateObjectLiteral [3], [5], #41 // Create a literal object in the heap {n: 2} and store the address in the accumulator. // accumulator: {n: 2}, r1 register: {n: 1} 000000550824fAF2@ 28:15 02 01 StaGlobal [2], [1] // Store the contents of accumulator ({n: 2}) in the global variable stack [2](a) // Accumulator: {n: 2}, r1 register: {n: 1}. 000000550824fAF5@31:26 F9 Star R2 // Store accumulator ({n: 2}) in r2 register // Accumulator: undefined, R1 register: {n: 1}, R2 register: {n: 2}.Copy the code

Bind the contents of the R2 register to the x property of {n: 1}. It can also be seen from here that operation a.x is register R1, which is {n: 1}.

000000550824Faf7@ 33:2d FA 04 06 StaNamedProperty R1, [4], [6] // Assign the constant pool value [4] to R1 1, x: {n: 2}}, R2 register: {n: 2}.Copy the code

Returns the value at the end.

[email protected] f9 fb Mov R2, r0 {n: 2} 000000550824fafe@40: 25 fb Ldar r0 // transfer register r0 to accumulator: {n: 2} 000000550824fb00@ 42: aa Return // Return the value of accumulator ({n: 2})Copy the code

So far, the analysis of this problem has been completed.

Finally, the concept of statements and expressions was mentioned earlier, so think about the result of the delete 0 expression at your leisure.

ECMAScript states that any expression evaluated is either a value or a reference. Delete 0 returns true because 0 itself evaluates to a literal expression, does not represent a real 0, and cannot be deleted from the system. This line of code actually does nothing.

I am not very good at the explanation of bytecode, I just read some articles to try to interpret, if there is any mistake welcome to point out.