在 Javscript 中,浏览器运行时解释器总是将字符串和数字视为不可变的吗?
当然,在可证明无害的情况下,他们会优化并将它们视为可变的。如果不是,为什么不呢?
例如,考虑简单的 for 循环。
for (let i = 0; i < 1000000000000; i++) {
console.log(i)
}
由于 i
变量的作用域为循环,并且循环中的任何代码都不需要 i 变量的“旧值”,因此浏览器简单地增加数字是有意义的符号 i
指向每次迭代。否则,新的内存字节流将被 i
的新值占用,这是无法想象的(“有人可能需要 i
的那些旧值!”) ).我们将在 for 循环(在内存中创建 i
的新值)和垃圾收集器(杀死 i
的所有旧值)之间进行不必要的竞争,这循环通常会获胜,并且我们会出现堆栈溢出。
哦,事情就是这样,不是吗。如果是这样,为什么浏览器在以其他方式优化代码时如此聪明,却如此愚蠢?
字符串也有类似的情况。请考虑以下事项。
{
let completeWorks = "This string dictates the complete works of William Shakespeare. To be or not to be that is the question whether it is nobler in the mind..."
completeWorks += "The End." // <-- what happens here?
}
字符串 completeWorks
是 block 范围的,并且可以证明只存在于这个 block 中。因此,当浏览器遇到指令 completeWorks += "The End"
时,它肯定会改变 completeWorks
。如果不是,为什么不呢?可能是他们不这样做的一个很好的理由,我想学习它。
最佳答案
(此处为 V8 开发人员——因此我对其他浏览器/引擎知之甚少。)
对此没有简单的答案;实现很复杂。
在 V8 中,字符串始终是不可变的(在创建之后)。一个原因是在堆上分配对象时,对象后面通常没有可用空间,所以我们不能只是将字符附加到现有字符串。另一个原因是,跟踪哪些字符串可以安全地发生变异会增加大量的复杂性(除了一些更容易检测的小众情况,但如果只支持这些情况,那么该机制将提供更少的值(value))。
V8 确实有一些巧妙的字符串操作技巧:当您使用较大字符串的子字符串时,不会复制任何字符;新字符串只是一个引用,上面写着“我是那边另一个字符串的长度 X 的一部分,从索引 Y 开始”。同样,当连接两个字符串时,如您的 completeWorks
示例,新字符串是一个引用,表示“我是其他两个字符串的连接”。 (为了完整起见,我会提到有最低字符数,低于该值这些技巧将不适用,因为简单地复制字符至少同样有效。)
数字比字符串对性能更敏感,也更容易处理。一般来说,堆分配的数字总是不可变的;但这还没有结束。 V8 大量使用“Smis”(“小整数”)的特殊表示形式,因为 JavaScript 程序中的许多数字都属于这个桶。 Smis 不是堆对象;创建一个新的和修改一个一样便宜,而且实际上是无法区分的(就像 C++ 中的 int
)。对于超出 Smi 范围的数字,优化编译器还会执行“转义分析”并可以“拆箱”非转义数字,这意味着将它们保存在 CPU 寄存器中(作为普通的 64 位 float )而不是在堆上分配它们首先,这又比改变其他不可变的堆对象更好。对于存储在对象属性中的数字的特殊情况,V8 也(在某些情况下)使用可变存储。
因此,您的问题的答案都是"is"(例如,当生成未优化的代码时,V8 不会花时间执行分析,因此代码必须保守地假设某处需要任何旧值),以及“不”(对于优化编译器,您的直觉是正确的,这应该是可以避免的;但这仍然并不意味着分配在堆上的任何数字都会在那里发生突变)。
Since the
i
variable is scoped to the loop
JavaScript 中的作用域很复杂。首先,没有 int i
。现在考虑一下:
for (var i = 0; i < 100; i++) {
// Use i here, or don't.
}
console.log(i); // Prints "100".
如果你的意思是 let i
,那么当然,你会有一个 block 范围的变量。在此示例中,性能将相同。
We will have an unnecessary race between the for loop (creating new values of
i
in memory) and the garbage collector (killing off all the old values ofi
), which the loop will generally win
没有。垃圾收集器具有高度自适应性,特别是当发生更多分配时它会做更多工作。没有办法“超越”它。如果需要,当垃圾收集器试图找到可以释放的内存时,程序会停止执行。
and we will have a stack overflow.
不,堆栈溢出与对象分配、垃圾回收或一般的堆内存无关。
关于javascript - 浏览器是否总是将 Javascript 中的字符串和数字视为不可变的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61627573/