为什么在 nodejs 的 for 循环中 let 比 var 慢? [英] Why is let slower than var in a for loop in nodejs?

查看:34
本文介绍了为什么在 nodejs 的 for 循环中 let 比 var 慢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了一个非常简单的基准:

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')

如果您运行 Chrome,您可以在这里尝试(因为 NodeJS 和 Chrome 使用相同的 JavaScript 引擎,尽管通常版本略有不同):

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({});

结果令我惊讶:

var: 89.162ms
let: 320.473ms

我已经在 Node 4.0.0 && 中对其进行了测试5.0.0 &&6.0.0,varlet的比例对于每个节点版本都是一样的.

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?

推荐答案

来自未来的提示:这些历史性能差异不再准确或相关,因为现代引擎可以优化let 语义在行为上没有明显差异时使用 var 语义.当存在可观察到的差异时,使用正确的语义对性能几乎没有影响,因为相关代码本质上已经是异步的.

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.

基于 varlet 的机制差异,运行时的这种差异是由于 var 存在于匿名函数的整个块作用域,而 let 仅存在于循环中,并且必须在每次迭代时重新声明.* 见下文 这是一个演示这一点的示例:

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
}());

注意 i 在循环的所有迭代中是共享的,而 let 不是.根据您的基准,node.js 似乎只是没有优化 let 的范围规则,因为它比 var 更新和复杂得多.

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.

这里是对 for 循环中的 let 的一个小外行解释,对于那些不想查看公认的密集规范但很好奇 let 为每次迭代重新声明,同时仍保持连续性.

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.

但是 let 不可能在每次迭代时重新声明,因为如果你在循环内改变它,它会传播到下一次迭代!

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);
  }
}());

您部分正确,因为这些更改尊重 j 的连续性.但是,它仍然在每次迭代时重新声明,如 Babel 所示:

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 提出了 有趣的一点:

Internet Explorer 14.14393 似乎没有这些 [性能] 问题.

Internet Explorer 14.14393 doesn't seem to have these [performance] issues.

不幸的是,Internet Explorer 未正确实施let 语法基本上使用更简单的 var 语义,因此比较其性能是一个有争议的点:

Unfortunately, Internet Explorer incorrectly implemented let syntax by essentially using the simpler var semantics, so comparing its performance is a moot point:

在 Internet Explorer 中,for 循环初始值设定项中的 let 不会为 ES2015 定义的每个循环迭代创建单独的变量.相反,它的行为就好像循环被包装在一个作用域块中,紧接在循环之前的 let .

In Internet Explorer, let within a for 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 the let immediately before the loop.


* 这个转译版本在 Babel 的 REPL 上演示了当你在 for 中声明一个 let 变量时会发生什么代码>循环.创建了一个新的声明性环境来保存该变量(详情),然后对于每次循环迭代 另一个声明性环境被创建来保存变量的每次迭代副本;每个迭代的副本都从前一个的值初始化(详细信息此处),但它们是独立的变量,正如每个闭包中输出的值所证明的那样.


* 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.

这篇关于为什么在 nodejs 的 for 循环中 let 比 var 慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆