Evaluation order
See why a + b * c still runs a first, why x += f() reads x before the call, and how ||, ?? and ?. skip work, with puzzles like a[i++] = i.
- 01Predict the orderSay which part of an expression runs first, and why precedence doesn't change that order.
- 02Trace assignments and callsFollow
=,+=, function arguments, and computed keys step by step, from left to right. - 03Spot skipped workTell when
&&,||,??, and?.skip the right side, and solve puzzles likea[i++] = i.
Which part of an expression runs first
An expression is a piece of code that gives a value, like 2 + 3 or getPrice(). Most expressions have several parts. JavaScript works out the parts one at a time, in a fixed order. Working out a part to get its value is called evaluating it. This lesson is about that order: which part runs first, and which runs next.
Often the order makes no difference. It matters when a part does something besides giving a value: it prints, changes a variable, or reads the next item from a list. That extra work is called a side effect. To make the order visible, many examples in this lesson use a small helper called say.
function say(label, value) {
console.log(label);
return value;
}
console.log(say("a", 2) + say("b", 3) * say("c", 4));Lines 1 to 4 make the helper. Line 2 prints the label it was given, and line 3 hands back the value. Many examples in this lesson start with the same four lines, so their own code begins on line 5.
Line 5 calls say three times. The * joins 3 and 4 before the + adds 2, so you might expect b and c to run first. They don't. The program prints a, then b, then c, and then 14.
a, then b, then c. Only then does JavaScript work out 3 * 4 = 12 and 2 + 12 = 14.You have a shopping list: bread, milk, eggs. A note at the bottom says "milk and eggs go in the same bag". You still walk down the list from the top and pick up bread first. The note only changes how the items are packed, not the order you pick them up.
- In real life: Pick up the items in the order they are written
- In JavaScript: Run each part from left to right:
a,b,c - In real life: The note says which items share a bag
- In JavaScript: Precedence and brackets say which values are joined
- In real life: The packed bags at the till
- In JavaScript: The final value,
14
Evaluation order is the order in which JavaScript works out the parts of an expression. The rule is simple: from left to right, one part at a time, and each part is finished before the next one starts. Precedence and brackets decide which values are joined together. They don't change which part runs first. A few operators, like &&, ||, ?? and ?., may skip a part completely.
The ECMAScript specification, the rulebook every JavaScript engine follows, writes this order down for every kind of expression. So Chrome, Firefox, Safari and Node.js all print the same lines, in the same order.
Left to right, whatever the precedence
Precedence is the rule that says which operator joins its values first. * comes before +, so 2 + 3 * 4 means 2 + (3 * 4). Precedence answers the question "which values belong together?" It doesn't answer "which part runs first?". That answer is always: the one on the left.
A replay of the example code, not a view inside the JavaScript engine.
script
function say(label, value) { console.log(label); return value;}Press Step to walk through line 5 of the first example. Each say call starts, prints its label, and hands back its value, from left to right. Only after all three calls does the last step work out 3 * 4 = 12 and 2 + 12 = 14. This replays the example code; it isn't a view inside the JavaScript engine.
function say(label, value) {
console.log(label);
return value;
}
console.log((say("a", 2) + say("b", 3)) * say("c", 4));Lines 1 to 4 make say. Line 5 puts grouping brackets around say("a", 2) + say("b", 3), so 2 and 3 are added first, and then multiplied by 4. The program prints a, b, c, and then 20. The answer changed from 14 to 20, but the three calls ran in the same order.
function say(label, value) {
console.log(label);
return value;
}
console.log(say("a", 2) ** say("b", 3) ** say("c", 2));Line 5 uses **, the power operator. It is unusual: it groups from the right, so 2 ** 3 ** 2 means 2 ** (3 ** 2), which is 2 ** 9, or 512. Even so, the calls still run a, b, c. The program prints those three labels, then 512.
The table below compares the three examples. Read each row as: "when I write this, the parts are grouped like this, and this part runs first."
| When you write | How the parts are grouped | Which part runs first |
|---|---|---|
a + b * c | b * c is grouped first, then added to a. | a runs first, then b, then c. |
(a + b) * c | The grouping brackets make a + b one group. | a runs first, then b, then c. |
a ** b ** c | b ** c is grouped first, because ** groups from the right. | a runs first, then b, then c. |
Next, here is how the specification describes it. Spec steps read like a recipe. "Let x be …" means "call the result x". A ? means "if this step goes wrong, stop and throw the error". "Evaluation" means "run this part", and GetValue means "read its value". After each box, the same steps follow in plain words.
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
// Shortened from the spec's steps for +, -, * and the other math operators.These are the spec's steps from EvaluateStringOrNumericBinaryExpression, which every math operator uses. In plain words, line by line:
- Run the left side. For
say("a", 2), that printsa. - Read the left side's value, 2, and keep it.
- Only now run the right side.
- Read the right side's value.
- Join the two values with the operator, such as
+or*, and hand back the answer. - The last line is a note, not a step.
So the left side is finished, value and all, before the right side starts. In a + b * c, the right side of + is the whole group b * c. That group runs second, and inside it, b runs before c.
Assignment: the left side goes first
An assignment writes a value into a place, like price = 4 or cart.price = 4. It's tempting to think the right side runs first, because its value is needed. But JavaScript first works out the left side, to know where to write.
const cart = { price: 3 };
function getCart() {
console.log("get cart");
return cart;
}
function getPrice() {
console.log("get price");
return 4;
}
getCart().price = getPrice();
console.log(cart.price);Line 1 makes a cart with a price of 3. Lines 2 to 5 make getCart, which prints "get cart" and hands back the cart. Lines 6 to 9 make getPrice, which prints "get price" and hands back 4.
Line 10 is the assignment. getCart() runs first and prints "get cart": JavaScript needs to know which object to write into. Then getPrice() runs and prints "get price". Then 4 is written into the cart's price. Line 11 prints 4.
Let lref be ? Evaluation of the left side.
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Perform ? PutValue(lref, rval).
Return rval.
// Shortened from the spec's steps for =.These are the spec's assignment steps. In plain words, line by line:
- Run the left side, only to find the place to write: "the
priceproperty, inside the cart". Nothing is read from it. - Run the right side.
- Read the right side's value, 4.
- Write that value into the place found in step 1. PutValue means "write into this place".
- The whole assignment gives back the value it wrote.
- The last line is a note, not a step.
So the place is found first, the value second, and the write happens last. The full steps also cover patterns like [a, b] = list; the box shows only the plain case.
Compound assignments, like +=, -= and *=, do one more thing: they read the old value. The question is when.
let total = 3;
function addTea() {
total = 10;
return 4;
}
total += addTea();
console.log(total);Line 1 gives total the value 3. Lines 2 to 5 make addTea: line 3 sets total to 10, and line 4 hands back 4.
Line 6 is total += addTea(). JavaScript reads the old total first, 3, and keeps it. Then it calls addTea(), which sets total to 10 and hands back 4. Then it adds the kept 3 and the 4, and writes 7 into total. Line 7 prints 7, not 14.
addTea() runs. So the 10 that addTea() writes is lost: step 3 writes 3 + 4 = 7 over it.A replay of the example code, not a view inside the JavaScript engine. The real += runs on a small helper that reports each read and write of total as it happens.
script
function addTea() { total = 10; return 4;}total += addTea();console.log(total);Step through line 6 and watch total. It is read as 3, changed to 10 by line 3, and then written over with 7. This replays the example code; it isn't a view inside the JavaScript engine. The real += runs on a small helper that reports each read and write as it happens.
let total = 3;
function addTea() {
total = 10;
return 4;
}
total = total + addTea();
console.log(total);This is the same program, but line 6 is written out in full: total = total + addTea(). The + runs its left side first, so total is read as 3 before addTea() runs. Line 7 prints 7 again. The long form and += follow the same order.
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
Perform ? PutValue(lref, r).
Return r.
// Shortened from the spec's steps for +=, -=, *= and the others.In plain words, line by line:
- Run the left side to find the place: the variable
total. - Read the old value from that place, 3, and keep it.
- Run the right side. This is where
addTea()is called and setstotalto 10. - Read the right side's value, 4.
- Join the kept 3 and the 4 with the operator: 3 + 4 = 7.
- Write 7 into the place from step 1.
- The whole assignment gives back 7.
- The last line is a note, not a step.
Step 2 comes before step 3. That's why a change made by the right side can't reach the old value: it was already read.
Function arguments and computed keys
A function call has parts too: the function itself, and each argument, the values inside the call brackets. JavaScript first finds the function, then runs the arguments from left to right, and only then makes the call.
function say(label, value) {
console.log(label);
return value;
}
function pickAdd() {
console.log("pick add");
return (x, y) => x + y;
}
console.log(pickAdd()(say("first", 2), say("second", 3)));Lines 1 to 4 make say. Lines 5 to 8 make pickAdd: line 6 prints "pick add", and line 7 hands back a small function that adds two numbers.
Line 9 calls pickAdd() first, to find the function to call. Then it runs the first argument, which prints "first", and the second, which prints "second". Then it calls the adding function with 2 and 3. The program prints "pick add", "first", "second", and then 5.
The arguments even run before JavaScript checks that the function really is a function. The next example calls a method that doesn't exist.
function say(label, value) {
console.log(label);
return value;
}
const tools = {};
try {
tools.format(say("argument", 3));
} catch (error) {
console.log(error.name);
}Line 5 makes an empty tools object, with no format method. Line 7 reads tools.format, which gives undefined. Then it runs the argument, and "argument" is printed. Only then does JavaScript check whether undefined can be called. It can't, so it throws a TypeError. Lines 8 and 9 catch the error and print its name, TypeError.
Let ref be ? Evaluation of MemberExpression.
Let func be ? GetValue(ref).
Let argList be ? ArgumentListEvaluation of Arguments.
If IsCallable(func) is false, throw a TypeError exception.
Return ? Call(func, thisValue, argList).
// Shortened from the spec's steps for a function call.These steps come from the spec's function call steps and EvaluateCall. In plain words, line by line:
- Run the part before the call brackets, like
tools.formatorpickAdd(). - Read its value and call it
func. Fortools.format, that isundefined. - Run every argument, from left to right, and keep their values in a list.
- Only now check that
funccan be called. If it can't, throw aTypeError. - Make the call, with the argument values from step 3.
- The last line is a note, not a step.
So a call to a missing method still runs its arguments first. If an argument prints, saves, or sends something, that happens before the error.
Objects follow the same left-to-right rule. A computed key is a property name in square brackets, like [name]: 3. JavaScript runs the code inside the brackets and uses the result as the property name.
function say(label, value) {
console.log(label);
return value;
}
const order = {
[say("key 1", "tea")]: say("value 1", 3),
[say("key 2", "cake")]: say("value 2", 4),
};
console.log(order.tea, order.cake);Line 5 starts the order object. Line 6 runs its key first, which prints "key 1" and gives "tea", and then its value, which prints "value 1" and gives 3. Line 7 does the same for the second property: "key 2", then "value 2". Line 9 prints 3 4.
So JavaScript doesn't run all the keys first and then all the values. It finishes one property, key and value, before it starts the next.
The table below collects the order for assignments, calls, and object properties. Read each row as: "when I write this, these parts happen in this order."
| When you write | What happens, in order |
|---|---|
x = f() | JavaScript finds where x lives, then calls f(), then writes the answer into x. |
getCart().price = getPrice() | getCart() runs first, then getPrice(), then the price is written into the cart. |
x += f() | JavaScript reads the old x first, then calls f(), then writes the sum into x. |
f(a, b) | JavaScript finds f first, then works out a, then b, then makes the call. |
{ [k]: v } | JavaScript works out each key, then that key's value, one property at a time. |
Skipping work: &&, ||, ?? and ?.
Some operators look at their left side first and may decide they don't need the right side at all. Skipping the right side like this is called short-circuiting. The skipped part doesn't run later. It never runs.
These operators check whether a value is truthy or falsy. A falsy value is one that counts as "no": false, 0, "", null, undefined, and NaN. Every other value is truthy.
function say(label, value) {
console.log(label);
return value;
}
const name = say("left", "Asha") || say("right", "guest");
console.log(name);Line 5 runs the left side first: it prints "left" and gives "Asha". "Asha" is truthy, so || already has its answer. The right side is skipped, so "right" is never printed. Line 6 prints Asha.
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
If ToBoolean(lval) is true, return lval.
Let rref be ? Evaluation of the right side.
Return ? GetValue(rref).
// The spec's steps for ||.These are the spec's steps for the logical operators. ToBoolean means "is this value truthy?". In plain words, line by line:
- Run the left side.
- Read its value.
- If it is truthy, hand it back straight away. The right side is never touched.
- Otherwise, run the right side.
- Hand back the right side's value.
- The last line is a note, not a step.
&& has the same shape, but it hands back the left value when it is falsy. ?? hands back the left value unless it is null or undefined. In all three, the left side always runs, and the right side runs only when it is needed.
Optional chaining, written ?., skips even more. If the value before ?. is null or undefined, the whole rest of the chain is skipped: property reads, calls, and the arguments of those calls.
function say(label, value) {
console.log(label);
return value;
}
const cart = null;
const price = cart?.getPrice(say("argument", 3));
console.log(price);Line 5 sets cart to null. Line 6 reaches cart?., sees null, and stops. getPrice is never looked up, and the argument never runs, so "argument" is never printed. price becomes undefined. Line 7 prints undefined, and that's the only line printed.
cart is null, so ?. stops right there and price becomes undefined. The argument is skipped too, so "argument" is never printed.Let baseReference be ? Evaluation of the part before ?.
Let baseValue be ? GetValue(baseReference).
If baseValue is either undefined or null, return undefined.
Return ? ChainEvaluation of the rest of the chain.
// Shortened from the spec's steps for ?.In plain words, line by line:
- Run the part before
?., herecart. - Read its value:
null. - If it is
nullorundefined, hand backundefinedstraight away. - Otherwise, run the rest of the chain, like
.getPrice(…)with its arguments. - The last line is a note, not a step.
Step 3 hands back before step 4, so nothing after ?. runs. That's different from cart.getPrice(…) without the question mark, which would read from null and throw a TypeError.
The same skipping works with assignment. ??= writes only when the current value is null or undefined. ||= writes only when it is falsy, and &&= only when it is truthy.
function say(label, value) {
console.log(label);
return value;
}
const cart = { price: 3 };
cart.price ??= say("default", 5);
cart.note ??= say("note", "fresh");
console.log(cart.price, cart.note);Line 5 makes a cart with a price of 3. Line 6 checks cart.price. It is 3, not missing, so the right side is skipped: "default" is never printed, and the price isn't written at all. Line 7 checks cart.note. It is undefined, so the right side runs, prints "note", and "fresh" is written. Line 8 prints 3 fresh.
The table below lists each operator that can skip work. Read each row as: "when I write this, the right side runs only in this case."
| When you write | When the right side runs |
|---|---|
a || b | Only when a is falsy, such as 0, "", null, or undefined. |
a && b | Only when a is truthy. |
a ?? b | Only when a is null or undefined. |
a ? b : c | Only one of b and c runs: b when a is truthy, c when it isn't. |
a?.b(c) | When a is null or undefined, nothing after ?. runs, not even c. |
x ??= y | Only when x is null or undefined. Otherwise x isn't written at all. |
Puzzles like a[i++] = i
Puzzles like a[i++] = i look like tricks, but the two rules you already know solve them: the left side runs first, and each part is finished before the next starts. They use two ways of adding 1 to a variable:
i++adds 1 toi, but gives back the old value.++iadds 1 toi, and gives back the new value.
const a = [0, 0, 0];
let i = 0;
a[i++] = i;
console.log(a.join(", "));
console.log(i);Line 1 makes an array a of three zeros. Line 2 sets i to 0.
Line 3 is the puzzle. The left side runs first: i++ gives the old value, 0, and sets i to 1. So the place to write is a[0]. Then the right side runs: i is now 1. So a[0] becomes 1. Line 4 prints 1, 0, 0, and line 5 prints 1.
const a = [0, 0, 0];let i = 0;a[i++] = i;console.log(a.join(", "));console.log(i);1, 0, 0011The left side picked index 0. Then the right side gave 1. So a[0] became 1, and i ended at 1.
Try all four buttons. Each one changes only line 3, and each run starts again from three zeros and i = 0:
a[i++] = i: the index is 0, theniis 1, soa[0]becomes 1.a[i] = i++: the index is 0, theni++gives the old 0, soa[0]stays 0. Nothing seems to change.a[++i] = i:++igives 1, so the index is 1, anda[1]becomes 1.a[i] = ++i: the index is 0, then++igives 1, soa[0]becomes 1.
In every case, i ends at 1. Only the place and the value change, and both follow from "left side first".
let x = 3;
x = x++;
console.log(x);Line 1 sets x to 3. Line 2 first finds the place to write, the variable x. Then the right side runs: x++ gives the old value, 3, and sets x to 4. Last, the = writes the 3 it was given into x, over the 4. Line 3 prints 3.
let i = 1;
const total = i++ + ++i;
console.log(total, i);Line 1 sets i to 1. Line 2 runs the left side of + first: i++ gives 1 and sets i to 2. Then the right side: ++i sets i to 3 and gives 3. So total is 1 + 3 = 4. Line 3 prints 4 3.
Some other languages, such as C, leave the order of some of these parts open, so a line like a[i++] = i can behave differently between compilers. JavaScript doesn't: the specification fixes the order, so every engine prints the same thing. Even so, in real code, put each change on its own line, so a reader doesn't have to solve a puzzle.
Reading input in the right order
In real code, the order matters most when a function reads the next item from somewhere: a list of numbers, a file, or a stream of messages. Each call moves a position forward, which is a side effect. So the call that runs first gets the first item.
const input = [3, 4];
let position = 0;
function next() {
const value = input[position];
position = position + 1;
return value;
}
const size = { width: next(), height: next() };
console.log(size.width, size.height);Line 1 holds the input: 3, then 4. Line 2 sets position to 0. Lines 3 to 7 make next: line 4 reads the item at the current position, line 5 moves the position forward by 1, and line 6 hands back the item.
Line 8 builds an object. Its properties run from top to bottom, one at a time, so width calls next() first and gets 3. Then height calls next() and gets 4. Line 9 prints 3 4. If you swap the two properties, the values swap too.
At a deli counter, you pull a numbered ticket from a machine. Each pull gives the next number. Who gets which number depends only on who pulled first.
- In real life: The first person to pull a ticket gets the first number
- In JavaScript: The first
next()call gets the first item,3 - In real life: The second person gets the next number
- In JavaScript: The second
next()call gets4 - In real life: Swap who pulls first, and the tickets swap
- In JavaScript: Swap
widthandheight, and the values swap
When the order of calls matters, you can make it obvious: call next() on its own line and save the result in a variable, then build the object from those variables.
f()inprice + f()f()inadd(1, f())f()in{ [f()]: 3 }f()inname || f()f()incart?.show(f())f()incart.note ??= f()
Put each call in its group: calls that always run, and calls that may be skipped.
Common mix-ups
Most mix-ups about evaluation order come from mixing up two questions: "which values belong together?" and "which part runs first?". Precedence answers the first. Evaluation order answers the second.
- "Precedence decides which part runs first." No. Precedence only groups the parts. In
a + b * c,astill runs first. - "An assignment runs its right side first." No. The left side runs first, to find the place to write. Only the write itself comes last.
- "
x += f()sees the changesf()makes tox." No. The oldxis read beforef()runs. - "Calling a missing method fails before its arguments run." No. The arguments run first. Then the "is it a function?" check throws a
TypeError. - "
?.only skips one property read." No. It skips the whole rest of the chain, including calls and their arguments. - "The order can change from one engine to another." No. The specification fixes it, and every engine follows it.
The table below puts the three ideas side by side. Read each row as: "this term means this, but it doesn't mean that."
| Term | What it means | What it doesn't mean |
|---|---|---|
| Precedence | Which parts of an expression belong together. | It doesn't decide which part runs first. Every operand still runs from left to right. |
| Evaluation order | Which part of an expression runs first. | It doesn't decide how the parts are grouped. That's the job of precedence and brackets. |
| Short-circuiting | Skipping the right side when the left side already decides the answer. | It doesn't run the right side "later". The skipped part never runs at all. |
Practice exercises
Guess the output of each short program before you run it. For each one, ask: which part is on the left, and is any part skipped? Try a guess first, then use the hints if you get stuck.
Line 5 subtracts a product from a number. Write the four printed lines in order, separated by commas.
function say(label, value) {
console.log(label);
return value;
}
console.log(say("x", 5) - say("y", 3) * say("z", 2));// x
// y
// z
// -1The three calls run from left to right, so x, y and z are printed first. Then * joins 3 and 2 into 6, and 5 - 6 is -1.
A cart gets one property with a computed key. Write the three printed lines in order, separated by commas.
function say(label, value) {
console.log(label);
return value;
}
const cart = {
[say("key", "tea")]: say("value", 3),
};
console.log(cart.tea);// key
// value
// 3Line 6 runs the key first, which prints key, and then the value, which prints value. The property is named tea and holds 3, so line 8 prints 3.
bump() changes count and also gives back a number. What does the last line print?
let count = 1;
function bump() {
count = 100;
return 2;
}
count *= bump();
console.log(count);count *= bump(); // reads 1, bump() gives 2, writes 1 * 2
console.log(count); // 2*= reads count as 1 first. Then bump() sets count to 100 and gives 2. Then 1 * 2 = 2 is written into count, over the 100.
load() counts how many times it runs. Two logical assignments may or may not call it. What does the last line print?
let calls = 0;
function load() {
calls = calls + 1;
return "tea";
}
const cart = { item: "cake" };
cart.item ||= load();
cart.note ??= load();
console.log(cart.item, cart.note, calls);cart.item ||= load(); // skipped: "cake" is truthy
cart.note ??= load(); // runs: note is undefined
console.log(cart.item, cart.note, calls); // cake tea 1The first line keeps "cake" and never calls load(). The second line calls it once and writes "tea". So calls is 1.
This program should read the width first and the height second, but it prints 4 3. Fix line 8. What does the fixed program print?
const input = [3, 4];
let position = 0;
function next() {
const value = input[position];
position = position + 1;
return value;
}
const size = { height: next(), width: next() };
console.log(size.width, size.height);const size = { width: next(), height: next() };
console.log(size.width, size.height); // 3 4Each property runs in the order it is written. With width first, it gets the first item, 3, and height gets 4.
Line 3 has two i++, one on each side of the =. What are the two printed lines?
const a = [0, 0, 0];
let i = 0;
a[i++] = i++;
console.log(a.join(", "));
console.log(i);a[i++] = i++; // writes 1 into a[0]
// 1, 0, 0
// 2The left i++ picks index 0 and moves i to 1. The right i++ gives 1 and moves i to 2. So a[0] becomes 1, and i ends at 2.
Check your understanding
For each question, ask two things. Which part is on the left, so it runs first? And does an operator like || or ?. skip a part? Each answer explains why it's right or wrong.
Question 1 of 7In
a + b * c, the*is grouped before the+. Which operand runs first?Choose an answer to see the explanation.
Question 2 of 7In what order do these power operands print?
Read the code, then predictfunction say(label, value) { console.log(label); return value; } console.log(say("a", 2) ** say("b", 3) ** say("c", 2));Choose an answer to see the explanation.
Question 3 of 7What does
x = x++leave inx?Read the code, then predictlet x = 3; x = x++; console.log(x);Choose an answer to see the explanation.
Question 4 of 7What does
i++ + ++igive whenistarts at 1?Read the code, then predictlet i = 1; const total = i++ + ++i; console.log(total, i);Choose an answer to see the explanation.
Question 5 of 7What does this optional call print when the cart is
null?Read the code, then predictfunction say(label, value) { console.log(label); return value; } const cart = null; const price = cart?.getPrice(say("argument", 3)); console.log(price);Choose an answer to see the explanation.
Question 6 of 7What does calling a missing method print here?
Read the code, then predictfunction say(label, value) { console.log(label); return value; } const tools = {}; try { tools.format(say("argument", 3)); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 7 of 7In
getCart().price = getPrice(), which function runs first?Choose an answer to see the explanation.
Key takeaways
JavaScript never guesses the order. It runs the parts of every expression from left to right, and only a few operators may skip a part.
- Evaluation order is the order in which JavaScript runs the parts of an expression: left to right, one part at a time.
- Precedence and brackets decide which values are joined. They don't change which part runs first.
- An assignment runs its left side first, to find the place to write, then its right side, and writes last.
x += f()reads the oldxbeforef()runs, so changes made byf()are written over.- A call finds the function, then runs its arguments from left to right, and only then checks that the function can be called.
- In an object, each computed key runs just before its value, one property at a time.
&&,||,??,? :and?.may skip a part completely. A skipped part never runs.
Remember the one-liner.
Grouping decides what is joined; the left side always runs first.
Coming next: test262 & conformance. It shows how the specification's rules, like the ones in this lesson, are tested in every JavaScript engine.