I have written a very simple benchmark:
console.time('var'); for (var i = 0; i < 100000000; i++) {} console.timeEnd('var') console.time('let'); for (let i = 0; i < 100000000; i++) {} console.timeEnd('let')
If you're running Chrome, you can try it here (since NodeJS and Chrome use the same JavaScript engine, albeit usually slightly different versions):
// Since Node runs code in a function wrapper with a different // `this` than global code, do that: (function() { console.time('var'); for (var i = 0; i < 100000000; i++) {} console.timeEnd('var') console.time('let'); for (let i = 0; i < 100000000; i++) {} console.timeEnd('let') }).call({});
And the results amaze me:
var: 89.162ms let: 320.473ms
I have tested it in Node 4.0.0 && 5.0.0 && 6.0.0 and the proportion between var
and let
is the same for each node version.
Could someone please explain to me what is the reason behid this seemingly odd behaviour?
In terms of performance comparison, var is faster and let is slower inside the loops while running or executing the code. Re-declaring var declared a variable in the same function or scope gives rise to Syntax Error whereas let declared variable cannot be redeclared.
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
After testing this in Chrome and Firefox, this shows that let is faster than var , but only when inside a different scope than the main scope of a function. In the main scope, var and let are roughly identical in performance. In IE11 and MS Edge, let and var are roughly equal in performance in both cases.
The execution context underlying how the JavaScript interpreter runs the code is basically the same when you use var compared to when you use let and const . That results in the same execution speed.
A note from the future: these historical performance differences are no longer accurate or relevant, as modern engines can optimize let
semantics by using var
semantics when there are no observable differences in behavior. When there are observable differences, using the correct semantics makes little difference in performance since the relevant code is already asynchronous in nature.
Based on the difference between the mechanics of var
vs. let
, this discrepancy in runtime is due to the fact that var
exists in the entire block scope of the anonymous function while let
exists only within the loop and must be re-declared for each iteration.* see below Here's an example demonstrating this point:
(function() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(`i: ${i} seconds`); }, i * 1000); } // 5, 5, 5, 5, 5 for (let j = 0; j < 5; j++) { setTimeout(function() { console.log(`j: ${j} seconds`); }, 5000 + j * 1000); } // 0, 1, 2, 3, 4 }());
Notice that the i
is shared across all iterations of the loop while let
is not. Based on your benchmark, it appears that node.js just hasn't optimized scoping rules for let
since it's much more recent and complicated than var
is.
Here's a little layman explanation of let
in for
loops, for those who don't care to look into the admittedly dense specs, but are curious how let
is re-declared for each iteration while still maintaining continuity.
But
let
can't possibly be re-declared for each iteration, because if you change it inside the loop, it propagates to the next iteration!
First here's an example that almost appears to validate this potential counter-argument:
(function() { for (let j = 0; j < 5; j++) { j++; // see how it skips 0, 2, and 4!?!? setTimeout(function() { console.log(`j: ${j} seconds`); }, j * 1000); } }());
You are partially right, in that the changes respect the continuity of j
. However, it is still re-declared for each iteration, as demonstrated by Babel:
"use strict"; (function () { var _loop = function _loop(_j) { _j++; // here's the change inside the new scope setTimeout(function () { console.log("j: " + _j + " seconds"); }, _j * 1000); j = _j; // here's the change being propagated back to maintain continuity }; for (var j = 0; j < 5; j++) { _loop(j); } })();
Derek Ziemba brings up an interesting point:
Internet Explorer 14.14393 doesn't seem to have these [performance] issues.
Unfortunately, Internet Explorer incorrectly implemented let
syntax by essentially using the simpler var
semantics, so comparing its performance is a moot point:
In Internet Explorer,
let
within afor
loop initializer does not create a separate variable for each loop iteration as defined by ES2015. Instead, it behaves as though the loop were wrapped in a scoping block with thelet
immediately before the loop.
* This transpiled version on Babel's REPL demonstrates what happens when you declare a let
variable in a for
loop. A new declarative environment is created to hold that variable (details here), and then for each loop iteration another declarative environment is created to hold a per-iteration copy of the variable; each iteration's copy is initialized from the previous one's value (details here), but they're separate variables, as proven by the values output within each closure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With