我不小心将一个大型数组和一个数字与<
进行了比较,JavaScript锁定了5秒钟以上。此比较的预期行为是什么?是否遍历整个数组? MDN并未说明情况。
作为一个具体示例,此代码段需要5秒钟才能打印出done
:
var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');
答案 0 :(得分:3)
Javascript“数组”(带有Array
原型的不是数组)只是对象,因此这是
var m = [];
m[268435461] = -1;
与
完全相同var m = {
"268435461": -1
}
除了在第一种情况下,m
具有Array
原型和特殊的length
属性。
但是,Array.prototype
中定义的方法(如forEach
或join
)正试图隐藏该事实并“模拟”顺序数组,就像其他语言中存在的那样。迭代其“ this”数组时,这些方法采用其length
属性,将循环计数器从0
增加到length-1
,并使用键String(i)
下的值进行操作(或undefined
(如果没有这样的密钥)
// 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引擎并不愚蠢,并且完全按照上述方法实现迭代。他们确实有特定于阵列的优化,但是这些优化针对的是“良好”的顺序阵列,而不是针对像这样的奇怪边缘情况。底线:不要那样做!