我不小心将一个大数组和一个数字与 <
进行了比较, JavaScript 锁定超过 5 秒。这种比较的预期行为是什么?它是否遍历整个数组? MDN没有说明情况。
作为一个具体的例子,这个代码片段需要超过 5 秒的时间来打印 done
:
var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');
最佳答案
Javascript“数组”(那些带有 Array
原型(prototype),而不是类型化数组)只是对象,因此这是
var m = [];
m[268435461] = -1;
完全一样
var m = {
"268435461": -1
}
除了第一种情况,m
有 Array
原型(prototype)和特殊 length
属性(property)。
但是,Array.prototype
中定义的方法(如 forEach
或 join
)试图隐藏这一事实并“模拟”顺序数组,因为它们存在于其他语言中。当迭代他们的“this”数组时,这些方法采用它的 length
属性,从 0
增加循环计数器最多 length-1
并用键下的值做一些事情 String(i)
(或 undefined
如果没有这样的 key )
// built-in js array iteration algorithm
for (let i = 0; i < this.length - 1; i++) {
if (this.hasOwnProperty(String(i))
do_something_with(this[String(i)])
else
do_something_with(undefined)
现在,length
正如名称所暗示的那样,数组的元素不是其中的元素数量,而是其键的最大数值 + 1,因此在您的情况下,length
将是 268435462
(检查它!)
当你做 m < 0
,即比较一个非数字和一个数字,JS将它们都转换为字符串,Array.toString
调用 Array.join
,它又使用上述循环将元素转换为字符串并在它们之间插入一个逗号:
// built-in js Array.join algorithm
target = '';
for (let i = 0; i < this.length - 1; i++) {
let element = this[String(i)]
if(element !== undefined)
target += element.toString()
target += ','
}
插图:
m = [];
m[50] = 1;
console.log(m.join())
这涉及大量内存分配,这就是导致延迟的原因。
(经过更多测试,分配不是这里的决定因素,“空心”循环会导致同样的减速:
console.time('small-init')
var m = [];
m[1] = -1;
console.timeEnd('small-init')
console.time('small-loop')
m.forEach(x => null)
console.timeEnd('small-loop')
console.time('big-init')
var m = [];
m[1e8] = -1;
console.timeEnd('big-init')
console.time('big-loop')
m.forEach(x => null);
console.timeEnd('big-loop')
话虽这么说,我不认为现代 JS 引擎那么愚蠢,并且完全按照上面描述的方式实现迭代。他们确实有特定于数组的优化,但这些优化针对的是“良好”的顺序数组,而不是像这样的奇怪的边缘情况。底线:不要那样做!
关于javascript - 为什么在 JavaScript 中比较数组和数字这么慢?它在做什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55363621/