Today I was hit by a bug in my JS code. Now that's not a particular miraculous event; those things happen frequently as you develop. This one was particular strange and I'd like to explain it to you. It's kind of a puzzle but hard to give in a small puzzle form. So excuse my contrived snippet here:
const LOG = 0;
const JUMP = 1;
const STOP = 2;
let arr = [LOG, JUMP, 2, 998, 999, LOG, STOP];
let pointer = 0;
function getOp() {
return arr[++pointer];
}
function getJumpSize() {
return arr[++pointer];
}
while (pointer < arr.length) {
let opCode = getOp();
switch (opCode) {
case LOG:
console.log('Current pointer:', pointer);
break;
case JUMP:
let delta = getJumpSize();
ASSERT(delta > 0, 'must always jump over something');
pointer += delta;
break;
case STOP:
return;
default:
throw new Error('You derailed');
}
}
This is the gist of it. When creating the dist build the build code will strip the ASSERT()
calls. The minifier will then optimize the JUMP
case to be simply pointer += getJumpSize()
(and mangle the names, of course). And now the code is broken. Why?
Scroll down for the answer.... Or try to figure it out, and good luck.
This should be enough whitespace.
The problem is related to ++x
vs x++
. While completely innocent, even when looking at it minified, there's a considerable difference between:
let x = f();
y += x;
// and
y += f();
WHEN the var is global. Or more precisely, when that global var is updated in the function call, shit hits a fan.
So what happens? When you do x += f();
the engine kind of "caches" the value of x
before calling f()
. So when f()
goes ahead to update it anyways, when it returns the value is lost because the cached value is used in the "compound assignment" (that's the +=
doohicky).
let x = 10;
function f() {
x = 20;
return 5;
}
let y = f(); // x = 20, y = 5
x += y; // "20 + 5", so x = 25
x = 10;
x += f(); // "10 + 5", so x is 15
And that was my bug of the day. What was yours?
I'm off to report a bug to a minifier.