为什么在nodejs的for循环中比var慢? [英] Why is let slower than var in a for loop in nodejs?
问题描述
我写了一个非常简单的基准:
console.time('var'); $ var $)
console.time('let );
for(let i = 0; i< 100000000; i ++){}
console.timeEnd('let')
如果您正在运行Chrome,可以在此处尝试(由于NodeJS和Chrome使用相同的JavaScript引擎,尽管通常略有不同):
//由于Node在函数包装器中运行与全局代码不同的//`this`的代码,所以:( function(){console.time('var'); for(var i = 0; i< 100000000; i ++){} console.timeEnd('var')console.time('let'); for = 0; i <100000000; i ++){} console.timeEnd('let')})。call({});
>
结果令我惊叹:
var:89.162ms
let:320.473ms
我已经在Node 4.0.0&& 5.0.0&& 6.0.0, var
和 let
之间的比例对于每个节点版本是相同的。
有人可以向我解释这个看似奇怪的行为是什么原因?
根据 var
与 let
之间的差异,这与 var
存在于匿名函数的整个块范围内,而 let
只存在于循环中,并且必须为每次迭代重新声明。 sup> 1 下面是一个演示这一点的例子:
(function() i = 0; i <5; i ++){setTimeout(function(){console.log(`i:$ {i} seconds`);},i * 1000);} // 5,5,5,5 ,5为(让j = 0; j <5; j ++){setTimeout(funct ion(){console.log(`j:$ {j} seconds`); },5000 + j * 1000); } // 0,1,2,3,4}());
请注意, i
在循环的所有迭代中共享,而 let
不是。基于您的基准测试,似乎node.js没有优化 let
的优化范围规则,因为它比 var
是。
详细说明
这是一个小的外行人解释让
在中为
循环,对于那些不关心公认的密集规范的人来说,好奇如何让
重新声明为每次迭代,同时仍保持连续性。
但是
let
不能为每次迭代重新声明,因为如果在循环中更改它,它会传播到下一个迭代!
首先这个例子几乎似乎验证了这个潜在的反驳:
(()的函数{for(let j = 0; j< 5; j ++){j ++; //看看它如何跳过0,2和4!? setTimeout(function(){console.log(`j:$ {j} seconds`);},j * 1000); }}());
这些变化尊重 j
的连续性。但是,如Babel所示,它仍然被重新声明:
function strict(function(){var _loop = function _loop(_j){_j ++; //这里是新范围内的更改setTimeout(function(){console.log(j:+ _j +seconds );},_j * 1000); j = _j; //这里是传播的变化以维持连续性}; for(var j = 0; j< 5; j ++){_loop(j);}}) );
复杂的规则。毫无疑问,基准测试表现出如此巨大的差距(现在)。希望将来会进一步优化。
1:见这个折叠版本在Babel的REPL上看到这一点。当您在中
循环时,会发生什么情况,就是创建一个新的声明性环境来保存该变量(详细信息),然后对于每个循环迭代 另一个声明环境被创建为保存变量的每个迭代副本;每个迭代的副本都是从上一个值初始化的(详细信息),但它们是单独的变量,如链接中由闭包输出的值所示。中声明
变量
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?
Based on the difference between the mechanics of var
vs. let
, it's related 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.1 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.
Elaboration
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);
}
})();
Like it was said. Complicated rules. It's no wonder that a benchmark shows such a large discrepancy in performance (for now). Hopefully it will be further optimized in the future.
1: See this transpiled version on Babel's REPL to see this demonstrated. What happens when you declare a let
variable in a for
loop like that is that 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 demonstrated in the link by the values output by the closures.
这篇关于为什么在nodejs的for循环中比var慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!