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:
- An object A is created and assigned to the variable foo
- The foo variable is assigned to bar
- 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
-
Let
lref
be the result of evaluating LeftHandSideExpression. -
Let
rref
be the result of evaluating AssignmentExpression. -
Let
rval
be ? GetValue(rref
). -
Perform ? PutValue(
lref
,rval
). -
Return
rval
.
Let’s analyze it according to the specification:
- The first thing I’m going to do is assign the expression on the left-hand side to
lref
, like this:lref = foo.x
- And then I’m going to take the right-hand side of the assignment expression, and I’m going to assign it to
rref
(Not counting at this point), like this:rref = (foo = {n: 2})
- Then through
GetValue(rref)
Gets the result of the assignment to the right-hand side of the expressionrval
, like this:rval = {n: 2} = (foo = {n: 2})
- Then use the
PutValue
Methods,rval
Assigns the result of thelref
, like this:
Lref = rval = foo. X = {n: 2}; {n: 1, x: {n: 2}}
Copy the code
- The last return
rval
, the first assignment expression ends. - 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:
- From the ECMscript specification:
- The first thing I’m going to do is assign the expression on the left-hand side to
lref
, like this:lref = foo.x
- And then I’m going to take the right-hand side of the assignment expression, and I’m going to assign it to
rref
(Not counting at this point), like this:rref = (foo = {n: 2})
- Then through
GetValue(rref)
Gets the result of the assignment to the right-hand side of the expressionrval
, like this:rval = {n: 2} = (foo = {n: 2})
- Then use the
PutValue
Methods,rval
Assigns the result of thelref
- The last return
rval
, the first assignment expression ends. - Then, starting from the first step, the second assignment expression is opened.
- The first thing I’m going to do is assign the expression on the left-hand side to
- Use operator priority:
The operator | priority |
---|---|
. |
20 |
= |
3 |
Member access. Has a higher priority than assignment, and the member access expression is assigned first.