preface

Recently, I prepared to summarize the knowledge of the front end, and suddenly SAW this problem:

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

At first glance, the brain does not turn over, a careful look also vaguely understand. So let’s go through this problem together.

The order in which the assignment operators operate

The precedence of the operators determines the order in which the operations in the expression are executed.

We will examine only the operator that is relevant here: assignment. Those of you who want to learn more can check out MDN.

Assignment order: right to left

Let’s start with the assignment operator:

'use strict'
const bar = new Proxy({ obj: { n: 1}}, {get: (_obj, prop) = > {
    console.log('get-----bar');
    return_obj[prop]; }}); foo = bar.obj;Copy the code

First we declare a bar object that uses a Proxy, and then assign the bar object’s obj property to an undeclared variable foo.

After running it, you can see that the log for the get method in Proxy is first printed, followed by an error indicating that the foo variable is not defined.

This verifies that the assignment operators do indeed operate from right to left.

Is it the same with continuous assignment? Let’s look at another example:

'use strict'
const bar = new Proxy({ obj: { n: 1}}, {get: (_obj, prop) = > {
    console.log('get-----bar');
    return_obj[prop]; }}); foo = test = bar.obj;Copy the code

Building on the example above, we used a continuous assignment at the end of the assignment, assigning bar.obj to two undeclared variables: test, foo. Run it, as shown below:

As you can see, the log for the Proxy get method is still printed, and the error message is that the test variable is not defined.

From this example, we can see that in continuous assignment operations, the order of operations is still from right to left. It can be viewed like this:

A = (B = C)

Parsing the topic

Let’s go back to the problem

'use strict'

var foo = {n: 1}; // A
var bar = foo; 
foo.x = foo = {n: 2}; // B
console.log(foo.x); 
console.log(bar);
Copy the code

Let’s first analyze the title:

  1. An object A is created and assigned to the variable foo
  2. The foo variable is assigned to bar
  3. A new object B is then created for continuous assignment

First we know that the foo variable holds a memory address of a reference type. We assign foo to bar, and bar holds the same memory address.

In the end, the continuous assignment operation is carried out. As we know from the above, the operation sequence of the continuous assignment operation is from right to left, which can be translated as follows:

foo.x = (foo = {n: 2});

According to the ECMScript specification:

LeftHandSideExpression = AssignmentExpression

  1. Let lref be the result of evaluating LeftHandSideExpression.

  2. Let rref be the result of evaluating AssignmentExpression.

  3. Let rval be ? GetValue(rref).

  4. Perform ? PutValue(lrefrval).

  5. Return rval.

Let’s analyze it according to the specification:

  1. The first thing I’m going to do is assign the expression on the left-hand side tolref, like this:lref = foo.x
  2. And then I’m going to take the right-hand side of the assignment expression, and I’m going to assign it torref(Not counting at this point), like this:rref = (foo = {n: 2})
  3. Then throughGetValue(rref)Gets the result of the assignment to the right-hand side of the expressionrval, like this:rval = {n: 2} = (foo = {n: 2})
  4. Then use thePutValueMethods,rvalAssigns the result of thelref, like this:
Lref = rval = foo. X = {n: 2}; {n: 1, x: {n: 2}}
Copy the code
  1. The last returnrval, the first assignment expression ends.
  2. Start the second assignment, in parentheses:
foo = {n: 2} // At this point, foo reassigns object B, disconnecting from object A
Copy the code

The result of the final continuous assignment is:

bar = {n: 1.x: {n: 2}};
foo = {n: 2};

console.log(foo.x); // undefined
console.log(bar); // {n: 1, x: {n: 2}}
Copy the code

Can we use the operator priority solution?

While looking at some data, I also saw that one of the solutions is based on operator priority.

Operator order:

The operator priority
. 20
= 3

The higher the priority of an operator, the higher the priority. Those of you who want to learn more can check out MDN.

Since the priority of accessing the member. Is higher than the priority of the assignment, the assignment looks like this:

foo.x = foo = {n: 2}

转为:

foo.x = {n: 2};
foo = {n: 2};
Copy the code

Such a solution turned out to be correct, but I was skeptical and experimented.

Experiment 1:

'use strict'

m.n = g = 1;
Copy the code

Two undeclared variables are assigned consecutively. If the priority assignment was used, m.n would be called first. M is not declared, so m is not defined. The results are as follows:

This proves that the higher-priority operator is indeed called first.

Experiment 2:

let a = new Proxy({n: 1}, {
  set: (obj, prop, value) = > {
    console.log('set-----a');
    obj[prop] = value;
    return true; }});const b = a;

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

console.log('a.x', a.x);
console.log('b', b);
Copy the code

In this example, we use Proxy to wrap the object pointed to by variable A, and when the attribute assignment of variable A is called, a log is printed.

Suppose the assignment order is as follows:

a = {}; b = a; A = b = c a = b = cCopy the code

Both A and C are copied as the new object C, and the connection with the original object is severed. In this case, logs in the Proxy will not be printed.

So let’s look at the results:

Finally the log is printed, indicating that the assignment order is:

First assign a.x: a.x = {n: 2}; Assign a to a: a = {n: 2};Copy the code

It turns out that using operator precedence is also the correct answer.

conclusion

The question can be answered in two ways:

  1. From the ECMscript specification:
    1. The first thing I’m going to do is assign the expression on the left-hand side tolref, like this:lref = foo.x
    2. And then I’m going to take the right-hand side of the assignment expression, and I’m going to assign it torref(Not counting at this point), like this:rref = (foo = {n: 2})
    3. Then throughGetValue(rref)Gets the result of the assignment to the right-hand side of the expressionrval, like this:rval = {n: 2} = (foo = {n: 2})
    4. Then use thePutValueMethods,rvalAssigns the result of thelref
    5. The last returnrval, the first assignment expression ends.
    6. Then, starting from the first step, the second assignment expression is opened.
  2. Use operator priority:
The operator priority
. 20
= 3

Member access. Has a higher priority than assignment, and the member access expression is assigned first.