Original author: Alexander Troup

Translation: Awkward and romantic

Solving a stupid JavaScript problem

What are the problems in this article that you are welcome to point out in the comments

It all started with a tweet from Tomasz Lakomy, who asked a question. If you are in an interview and the interviewer asks you such a question, what should you do?

As for how to deal with this question in an interview, I think it depends on the focus of the question. If the question focuses on what the final value of the tree variable is, I can simply tell the interviewer to go away, or I might copy it to the browser console and run it, see what happens, and then tell the interviewer to go away.

However, if the point is that the interviewer wants to see how you would solve the problem, then this is much more interesting, and leads to a lot of weird things about the JavaScript language and how compilers work. So in this article, I’m going to delve into this weird thing and see what interesting things can come out of it.

I also posted a Twitch post detailing the process for fixing the problem. It’s a long article, but it gives you another perspective on how to solve these problems step by step.

The whole idea

Let’s first copy it into the code box so that readers can copy and paste it into the console for execution.

let b = 3, d = b, u = b;
const tree = ++d * d*b * b++ +
              + --d+ + +b-- +
                 + +d*b+ +
                     u
Copy the code

I noticed a few things at first glance. First, there are a few tricks for JavaScript compilers. As we all know, JavaScript compilers usually add a semicolon to the end of each line unless the expression on the current line is incomplete. In this code, the + at the end of each line tells the compiler not to terminate the statement.

All the first line of the code does is create three variables and assign them all a value of 3. 3 is the initial value of b, so when we copy the value of B to another variable, we first create a new variable, and then assign 3 to the new variable. If JavaScript assigns values to variables by reference, each new variable will refer to the previously used variable, but will not create a value of its own.

Read more: Value passing and reference passing in JavaScript

Operator precedence and associativity

The following concepts are key to solving this problem. I’ll explain each of these concepts, but in general, they determine the order in which a JavaScript expression is evaluated.

Operator priority

Question: What is the difference between the following two expressions?

3 + 5 * 5

5 * 5 + 3
Copy the code

The result of both expressions is the same, and anyone who has studied the most basic mathematics knows that multiplication takes precedence over addition. A formula called BODMAS is called Bracket, Order, Division & Division using a multi sign, and then Addition & Subtraction. In JavaScript, there is a similar concept called operator precedence, which refers to the order in which we evaluate expressions. If we want to calculate 3 + 5 first, we just need to do the following:

( 3 + 5 ) * 5
Copy the code

Since the () operator has precedence over *, the expressions in parentheses are evaluated first.

Every JavaScript operator has a priority, and with so many operators in the expression we just solved, the first thing we need to do is see which operators have a higher priority and are evaluated first. In particular, the ++ and — operators change the values of variables B and D, and we need to know in what order they are evaluated compared to the rest of the expression.

Read more: JavaScript operator priority table

associativity

Associativity is used to determine which operators have the same precedence in an expression.

a + b + c
Copy the code

In the above expression, there is no need to determine the precedence of the operator, because we have only one operator. So, do we do it in terms of a plus b plus c or do we do it in terms of a plus b plus c?

I know the final answer is the same, but the compiler needs to know so it can calculate the result of the expression and move on to the rest. In this case, the compiler chooses the (a+b)+c option because the + operator is left associative, meaning that it first evaluates the left (first) side of two operators of the same precedence.

“Why not make all the operators left associative?” “You may ask.

Let’s look at this example:

a = b + c
Copy the code

If we make all operators left associative, we get:

(a = b) + c
Copy the code

It just seems so weird, and it’s not what I’m trying to say. We need to do this if we want this expression to be implemented using only the left associative.

a + b = c
Copy the code

So this becomes a plus b is equal to c, or more specifically, we do a plus b first, and then we assign c to that result.

The JavaScript compiler would be completely blindsided if we did this, and the reason we use different associativity for different operators is to make our code more readable. The order of expressions is more acceptable when you read a = b + c. Although it actually uses both the left-associative (+) and right-associative (=) operators, it is more natural and acceptable to read.

You may have noticed by now that there is a associative problem with a = b + c. When two operators have different associativity, how do you know which part of the expression to evaluate first? The answer is that whichever operator has a higher priority is calculated first! In this case, the + operator takes precedence, so it is executed first.

I added a more detailed explanation in the closing notes.

Understand how the expression was evaluated

Now that we have a basic understanding of these concepts, we can begin to solve our problems. There are so many different operators in the expression just described that the absence of parentheses to help understand it is a strain on our fragile brains. So let’s add some parentheses to help understand. All we need to do is list all the operators used, along with their priorities and associativity.

Variables x, y, and operators Priority (A higher value indicates a higher priority) Associativity (left/right)
x++ 18 Not related to
x– 18 Not related to
++x 17 right
–x 17 right
+x 17 On the left
* 15 On the left
x + y 14 On the left
= 3 right

parentheses

It’s worth mentioning here that getting the parentheses in the right place can be tricky. I can guarantee that MY calculations are correct every step of the way, but that doesn’t guarantee that MY parentheses are always in the right place! If anyone knows of a tool that can automatically close parentheses, please let me know.

Let’s be clear about the order in which the expressions are evaluated, and add parentheses to help. I’ll show you step by step how I arrived at the final result, basically following a step-by-step calculation of priorities.

To deal withx++Operators andx--The operator

const tree = ++d * d*b * (b++) +
 							+ --d+ + +(b--) +
 									+ +d*b+ +
 											u
Copy the code

To deal with+,++x,--xThe operator

We have a little problem here, and I’m going to compute the + operator, and we’ll solve it later when we run into problems.

const tree = ++d * d*b * (b++) +
 							+ --d+ (+(+(b--))) +
 									(+(+(d*b+ (+
 												u))))
Copy the code

Now we get to the tricky part.

const tree = ++d * d*b * (b++) +
              + --d+ (+(+(b--))) +
                  (+(+(d*b+ (+
                        u))))
Copy the code

I highlighted the problem (+ –d at the beginning of the second line). — has the same priority as +(). So, in what order should we calculate it? Let me put this in a simpler way.

let d = 10
const answer = + --d
Copy the code

It is important to note that the + here is not additive (binary operation), but unary operation +, which means positive number. It’s no different than minus 1, except it’s plus 1.

Of the three plus signs in the middle of the second line, only the first one is binary, and the next two are unary.

The answer is that we evaluate from right to left (subtracting, then adding) because the precedence operators are right-associative.

Because unary operators need an operand

So this part of the expression is written as + (–d).

To help you understand, imagine that all operators are the same, in which case + +1 is the same as (+ (+1)), and in the same way 1-1-1 is the same as (1-1)-1). Do you notice the difference between the right associative operator and the left associative operator?

Applying this logic to our expression evaluation problem, we get:

const tree = ++d * d*b * (b++) +
             (+ (--d)) + (+(+(b--))) +
                 (+(+(d*b+ (+
                       u))))
Copy the code

Finally add the parentheses of the ++x operator:

const tree = (++d) * d*b * (b++) +
         (+ (--d)) + (+(+(b--))) +
             (+(+(d*b+ (+
                   u))))
Copy the code

To deal with*The operator

We’re going to do associativity again, but this time it’s the same operator, and it’s all left associative. Compared to the last step, this is a piece of cake!

const tree = ((((++d) * d) * b) * (b++)) +
                 (+ (--d)) + (+(+(b--))) +
                    (+(+((d*b) +
                          (+u))))
Copy the code

We’ve reached the point where we can start counting. We could have added extra parentheses to the assignment operator, but I thought that would be gilding the lily, so I didn’t use it. Note that the above expression is just a more complex x = a + b + c.

We could actually omit some unary operators, but I’m going to leave them in for now, just in case.

By splitting the expression into parts, we can start with each part and work our way up.

let b = 3, d = b, u = b;
 
const treeA = ((((++d) * d) * b) * (b++))
const treeB = (+ (--d)) + (+(+(b--)))
const treeC = (+(+((d*b) + (+u))))
const tree = treeA + treeB + treeC
Copy the code

Now that we’ve done that, we can evaluate the parts. Starting from the treeA

Calculate the TreeA

let b = 3, d = b, u = b;
const treeA = (((++d) * d) * b) * (b++)
Copy the code

So the first thing you do is you compute plus plus d, which will both return 4 and add d to itself.

// b = 3
// d = 4

((4 * d) * b) * (b++)
Copy the code

And then we have 4 times d, and we know that d is 4, so 4 times 4 is 16

// b = 3
// d = 4

(16 * b) * (b++)
Copy the code

Now, the interesting thing is, we’re going to multiply b before we add one to b, because we’re going to do it from left to right. 16 times 3 is 48

// b = 3
// d = 4

48 * (b++)
Copy the code

Earlier we said that ++ has higher operator precedence than *, so it could be written as 48 * b++, but it’s worth noting here because b++ returns the value first and then increments itself, rather than first and then increments itself. So even though b ends in 4, we’re going to be multiplying by 3.

// b = 3
// d = 4

48 * 3

// b = 4
// d = 4
Copy the code

And 48 times 3 is 144, so we’re done with the first part, and then both b and D are 4, so it’s 144

let b = 4, d = 4, u = 3;
 
const treeA = 144
const treeB = (+ (--d)) + (+(+(b--)))
const treeC = (+(+((d*b) + (+u))))
const tree = treeA + treeB + treeC
Copy the code

Calculate TreeB

const treeB = (+ (--d)) + (+(+(b--)))
Copy the code

In this expression, we see that the unary operator is not really useful. If we omit them, we can greatly simplify the expression.

// b = 4
// d = 4

const treeB = (--d) + (b--)
Copy the code

The rest of the expressions will be familiar. –d will return 3, and b– will return 4. After the expression is evaluated, the values of b and D are both 3.

const treeB = 3 + 4

// b = 3
// d = 3
Copy the code

So the original problem was simplified to this:

let b = 3, d= 3, u = 3;
 
const treeA = 144
const treeB = 7
const treeC = (+(+((d*b) + (+u))))
const tree = treeA + treeB + treeC
Copy the code

Calculate TreeC

Home court at last!

// b = 3
// d = 3
// u = 3

const treeC = (+(+((d*b) + (+u))))
Copy the code

Let’s start by getting rid of those pesky unary operators

// b = 3
// d = 3
// u = 3

const treeC = (+(+((d*b) + u)))
Copy the code

I got one. There’s a lot of parentheses here, so be careful.

// b = 3
// d = 3
// u = 3

const treeC = (d*b) + u
Copy the code

And then it’s easy. 3 times 3 is 9,9 plus 3 is 12, and we have one more step to go.

The answer is there!

let b = 3, d= 3, u = 3;
 
const treeA = 144
const treeB = 7
const treeC = 12
const tree = treeA + treeB + treeC
Copy the code

144 + 7 + 12 is 163. The answer is 163.

conclusion

JavaScript often gives you a mixed blessing. But if you understand how languages are organized, you can understand why they are the way they are at the bottom.

What’s more, the process of solving a problem can be more illuminating than the answers, and the solutions you come up with as you solve them can be rewarding.

It’s worth noting that I used the browser console constantly to verify my reasoning and focused on extrapolating backwards from the results rather than thinking forward.

Even if you know how to solve this problem, in the process of solving the problem also encountered a lot of grammatical ambiguity, I believe you will have the same question when looking at this tree (expression)! I’ve documented a few of them, but each is worth a separate article!

I would also like to give credit to AnthonyPAlicea, without whose course I would never have figured this stuff out, and to tlakomy for asking the question.

I have set up a technical communication group. Every day, I will send small English cards related to technical terms in the group. We can add “Xiedaimala03” into the group.

Welcome to pay attention to the public account “front view” to get daily English cards.