Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is let not slower than var?

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 with var) lives within the entire function
  • j (declared with let) lives only within the for 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:

  1. In the case of var, these steps are performed:

IterationStatement : for ( Expressionopt ; Expressionopt ; Expressionopt ) Statement

  1. If the first Expression is present, then
    • Let exprRef be the result of evaluating the first Expression.
    • Let exprValue be GetValue(exprRef).
    • ReturnIfAbrupt(exprValue).
  2. Return ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet).
  1. 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

    1. Let oldEnv be the running execution context’s LexicalEnvironment.
    2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).
    3. Let isConst be the result of performing IsConstantDeclaration of > 1. LexicalDeclaration.
    4. Let boundNames be the BoundNames of LexicalDeclaration.
    5. 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.
    6. Set the running execution context’s LexicalEnvironment to loopEnv.
    7. Let forDcl be the result of evaluating LexicalDeclaration.
    8. If forDcl is an abrupt completion, then
      • Set the running execution context’s LexicalEnvironment to oldEnv.
      • Return Completion(forDcl).
    9. If isConst is false, let perIterationLets be boundNames otherwise let perIterationLets be « ».
    10. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet).
    11. Set the running execution context’s LexicalEnvironment to oldEnv.
    12. 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
like image 309
Adelin Avatar asked Jul 26 '18 05:07

Adelin


1 Answers

I agreed with @Derek 朕會功夫 that it is really hard to reason without diving down into the implementation.

But, you might have missed the some of the steps in case of var:

In the case of var, there are actually more steps performed:

IterationStatement : for ( Expressionopt ; Expressionopt ; Expressionopt ) Statement

  1. If the first Expression is present, then
    • Let exprRef be the result of evaluating the first Expression.
    • Let exprValue be GetValue(exprRef).
      • ReturnIfAbrupt(V).
      • If Type(V) is not Reference, return V.
      • Let base be GetBase(V).
      • If IsUnresolvableReference(V), throw a ReferenceError exception.
      • If IsPropertyReference(V), then
        • If HasPrimitiveBase(V) is true, then
          • Assert: In this case, base will never be null or undefined.
          • Let base be ToObject(base).
        • Return base.[[Get]](GetReferencedName(V), GetThisValue(V)).
      • Else base must be an Environment Record,
        • Return base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).
    • ReturnIfAbrupt(exprValue).
  2. Return ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet).

The slightly additional processing could be from GetValue.

like image 179
Jee Mok Avatar answered Oct 20 '22 21:10

Jee Mok