To make an extreme sum up, the difference between var
and let
is their life within a scope.
So if we are to take the example from this answer:
(function() {
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(`i: ${i}`);
}, i * 100);
}
// 5, 5, 5, 5, 5
for (let j = 0; j < 5; j++) {
setTimeout(function() {
console.log(`j: ${j}`);
}, 1000 + j * 100);
}
// 0, 1, 2, 3, 4
}());
i
(declared withvar
) lives within the entirefunction
j
(declared withlet
) lives only within thefor
loop.
To me, this means that javascript, after each iteration, besides declaring and assigning to a variable, in the case of let
it also needs to perform an extra step: clean up j
But if I'm reading the specs right, there's so much more:
- In the case of
var
, these steps are performed:
IterationStatement : for ( Expressionopt ; Expressionopt ; Expressionopt ) Statement
- If the first Expression is present, then
- Let exprRef be the result of evaluating the first Expression.
- Let exprValue be GetValue(exprRef).
- ReturnIfAbrupt(exprValue).
- Return ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet).
But in the case of
let
, a whopping list of 12 steps are performed, including creations of new declarative environments.IterationStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement
- Let oldEnv be the running execution context’s LexicalEnvironment.
- Let loopEnv be NewDeclarativeEnvironment(oldEnv).
- Let isConst be the result of performing IsConstantDeclaration of > 1. LexicalDeclaration.
- Let boundNames be the BoundNames of LexicalDeclaration.
- For each element dn of boundNames do
- If isConst is true, then
- Perform loopEnv.CreateImmutableBinding(dn, true).
- Else,
- Perform loopEnv.CreateMutableBinding(dn, false).
- Assert: The above call to CreateMutableBinding will never return an abrupt completion.
- If isConst is true, then
- Set the running execution context’s LexicalEnvironment to loopEnv.
- Let forDcl be the result of evaluating LexicalDeclaration.
- If forDcl is an abrupt completion, then
- Set the running execution context’s LexicalEnvironment to oldEnv.
- Return Completion(forDcl).
- If isConst is false, let perIterationLets be boundNames otherwise let perIterationLets be « ».
- Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet).
- Set the running execution context’s LexicalEnvironment to oldEnv.
- Return Completion(bodyResult).
So why is it that when running the following test (which I took from the same question I previously referenced) , var
performs slower than let
, and not vice versa, as I'm expecting?
(function() {
let varTime = performance.now()
for (var i = 0; i < 100000000; i++) {}
varTime = performance.now() - varTime;
console.log('var', varTime)
let letTime = performance.now()
for (let i = 0; i < 100000000; i++) {}
letTime = performance.now() - letTime;
console.log('let', letTime)
}).call({});
TEST 1
var: 147.500ms
let: 138.200ms
TEST 2
var: 141.600ms
let: 127.100ms
TEST 3
var: 147.600ms
let: 122.200ms