ES6 JavaScript - 循环内使用const还是循环外使用let?

17

出于性能考虑,我想了解 ES6 JavaScript 中以下代码的区别:

var list = [...];
let item; //let outside the loop
for (let i = 0; i < list.length; i++) {
    item = list[i];
}

var list = [...];
for (let i = 0; i < list.length; i++) {
    const item = list[i]; //const inside the loop
}
假设变量item在循环内部需要保持不变。
对于这个场景,有哪些建议?每种方法的优缺点是什么?GC在处理它们时是否有差异?
请注意,这只是微小的优化。此外,性能是相对于所使用的JS引擎而言的。(参见答案)

1
过早地进行优化是万恶之源。 - Barmar
@Barmar 这不是过早优化。我已经编写了代码,进行了调试等等...现在我知道它是可靠的,我想要最大化它的效率,因为它在我的程序中被广泛使用。 - Max K
1
@MaxK 你的性能测试是否表明这是瓶颈?如果你还没有将其识别为需要进行调优的地方,那么这就是过早的优化。 - Barmar
1
将某物标记为常量会产生垃圾回收成本。虚拟机中的对象会被特殊处理;而在每个循环结束时分配并释放一个项目的成本似乎不合理。尽管在少数迭代中影响可能很小,但这似乎是一种反优化。 - Stand__Sure
1
for (let item, i = 0; - Slai
显示剩余4条评论
3个回答

9

考虑到不同浏览器具有完全不同的内部实现,给出一个明确的答案可能会很困难。 很可能没有任何区别。在执行之前,浏览器中的JavaScript会被内部JIT编译器编译,该编译器很可能会识别出循环内部的冗余变量声明并进行优化,就像其他好的编译器一样。使用letconst肯定会对此产生影响,我认为使用const会更有可能使编译器优化掉循环,因为编译器可以立即看到它是一个不可变的原子变量,仅限于内部循环范围。

还有可能对性能密集型循环进行展开。不过,JavaScript还有其他一些性能问题,例如在更高的作用域中访问变量会带来轻微的性能损失,我记得很久以前在浏览器中进行游戏开发时曾研究过这个问题。这可能已经不再适用于当前情况,但几年前确实如此。

正如其他人指出的那样,除非分析已经表明这是应用程序中的一个严重瓶颈,否则属于“过早优化”。我非常怀疑优化这部分代码是否会带来任何显著的性能提升。如果在这个领域性能很重要,最好的建议是自己对不同情况进行分析,并根据你的用例决定使用什么方法最佳。

为了可读性和最佳实践,通常最好将变量的作用范围尽可能限制在最小范围内。通过在循环内部使用const来声明变量。如果在循环外部使用let声明变量,可能会导致错误,因为您后面可能会使用该变量。

1
这只是猜测,没有实际测试,但我认为如果你分配了 const item = list[i];,JIT 编译器会看到它是一个原子常量变量,直到超出范围并被垃圾回收之前保持不变。它可能会检查对 list[i] 的修改,如果没有修改,它可能会将整个步骤优化掉并直接引用 list[i]。这就是大多数编译器的处理方式。 - ajxs
那是有道理的。如果list的值存在差异,是否会影响编译(例如,list = ['one', 1, {}, Array(5), function(){}])? - vol7ron
我已经深入研究了有关字节码和Turbofan的旧文章:P 如果我晚些时候有时间,我会尝试调查V8的工作原理。 - vol7ron
当然!它必须编译您打算在变量上使用的代码,如果它们是同类类型,那么它肯定可以创建更优化的代码。再次强调,这只是猜测。但它似乎与我们已知的信息一致。https://blog.ghaiklor.com/how-v8-optimises-javascript-code-a0f3bbd46ac9 - ajxs
虽然这并没有直接的相关性,但它提供了一些间接的背景信息:https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775 - vol7ron
显示剩余2条评论

2
在第一个示例中,item在循环外部作用域范围内,因此分配的最终值将在封闭范围内可用。在后面的示例中,item在循环关闭后未定义。您还将无法在循环内重新分配item,因为它是const而不是let
个人而言,我会使用第二个示例,除非有理由使用第一个示例。性能差异将是微不足道的,但第二个示例略微更糟,因为需要额外的变量分配。

0

免责声明:我现在认为以下内容是推测,不建议将其选为答案,直到可以确认为止。在此之前,我将保留它以供评论和确认;如果发现不正确,将被删除。


有两个问题需要考虑:

  1. letconst的区别
  2. 循环内部的变量声明

两者都取决于浏览器的实现,因此性能并不一定;然而,通常第一个问题(在循环外声明let)应该会产生更高效的结果,因为声明变量通常比仅进行值分配要花费更多OPS。

值得注意的是,结果的效率受到几个因素的影响。其中包括但不限于:

  • 用于执行代码的系统资源
  • 所执行的操作类型(如果与初始示例不同)
  • 列表的长度和数值的大小

虽然很难确定代码会更加有效率多少,但是值得注意的是,在普通条件下(至少具有2GB RAM的普通多核台式机)和中等大小的列表(例如5k个元素),结果之间的差异将会是纳秒或微秒级别,并且考虑到这种差异通常被认为是一种微小优化。


@Fabio 谢谢,我会尝试更新并努力编辑以澄清。 - vol7ron
1
@ajxs,您说得对。在字节码层面上,“const”可能更快,因为它预编译了资产。我曾犹豫是否建议这样做,但我认为根据旧信息,“let”被认为更有效率,而“var”则比它更高。不过,我可能会删除这个答案,因为它在实施方面非常确定。 - vol7ron
@ajxs 现在我完全不确定了。我会更新这个并建议在确认了这些声明之前不要选择它作为答案。在那之前,这只是猜测。const在循环块内部,这就解释了为什么你能够在每次迭代中使用它并赋值(在其他地方使用const会因重新分配错误而失败)。话虽如此,我认为它没有利用预编译的优势。 - vol7ron
@vol7ron 你知道在Chromium浏览器中哪个例子更有效吗?无论如何,我稍后会运行测试来亲自验证。 - Max K
1
通常情况下,NodeJS(而不是微软的ChackraCore)运行V8并可用于性能分析。 - vol7ron
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接