Statement Ordering
The order in which we express statements in our code is not necessarily the same order as the JS engine will execute them. That may seem like quite a strange assertion to make, so we’ll just briefly explore it.
But before we do, we should be crystal clear on something: the rules/grammar of the language (see the Types & Grammar title of this book series) dictate a very predictable and reliable behavior for statement ordering from the program point of view. So what we’re about to discuss are not things you should ever be able to observe in your JS program.
Warning: If you are ever able to observe compiler statement reordering like we’re about to illustrate, that’d be a clear violation of the specification, and it would unquestionably be due to a bug in the JS engine in question — one which should promptly be reported and fixed! But it’s vastly more common that you suspect something crazy is happening in the JS engine, when in fact it’s just a bug (probably a “race condition”!) in your own code — so look there first, and again and again. The JS debugger, using breakpoints and stepping through code line by line, will be your most powerful tool for sniffing out such bugs in your code.
Consider:
var a, b;
a = 10;
b = 30;
a = a + 1;
b = b + 1;
console.log( a + b ); // 42
This code has no expressed asynchrony to it (other than the rare console
async I/O discussed earlier!), so the most likely assumption is that it would process line by line in top-down fashion.
But it’s possible that the JS engine, after compiling this code (yes, JS is compiled — see the Scope & Closures title of this book series!) might find opportunities to run your code faster by rearranging (safely) the order of these statements. Essentially, as long as you can’t observe the reordering, anything’s fair game.
For example, the engine might find it’s faster to actually execute the code like this:
var a, b;
a = 10;
a++;
b = 30;
b++;
console.log( a + b ); // 42
Or this:
var a, b;
a = 11;
b = 31;
console.log( a + b ); // 42
Or even:
// because `a` and `b` aren't used anymore, we can
// inline and don't even need them!
console.log( 42 ); // 42
In all these cases, the JS engine is performing safe optimizations during its compilation, as the end observable result will be the same.
But here’s a scenario where these specific optimizations would be unsafe and thus couldn’t be allowed (of course, not to say that it’s not optimized at all):
var a, b;
a = 10;
b = 30;
// we need `a` and `b` in their preincremented state!
console.log( a * b ); // 300
a = a + 1;
b = b + 1;
console.log( a + b ); // 42
Other examples where the compiler reordering could create observable side effects (and thus must be disallowed) would include things like any function call with side effects (even and especially getter functions), or ES6 Proxy objects (see the ES6 & Beyond title of this book series).
Consider:
function foo() {
console.log( b );
return 1;
}
var a, b, c;
// ES5.1 getter literal syntax
c = {
get bar() {
console.log( a );
return 1;
}
};
a = 10;
b = 30;
a += foo(); // 30
b += c.bar; // 11
console.log( a + b ); // 42
If it weren’t for the console.log(..)
statements in this snippet (just used as a convenient form of observable side effect for the illustration), the JS engine would likely have been free, if it wanted to (who knows if it would!?), to reorder the code to:
// ...
a = 10 + foo();
b = 30 + c.bar;
// ...
While JS semantics thankfully protect us from the observable nightmares that compiler statement reordering would seem to be in danger of, it’s still important to understand just how tenuous a link there is between the way source code is authored (in top-down fashion) and the way it runs after compilation.
Compiler statement reordering is almost a micro-metaphor for concurrency and interaction. As a general concept, such awareness can help you understand async JS code flow issues better.