为什么在JavaScript中比较数组和数字这么慢?到底在做什么

时间:2019-03-26 18:03:44

标签: javascript arrays

我不小心将一个大型数组和一个数字与<进行了比较,JavaScript锁定了5秒钟以上。此比较的预期行为是什么?是否遍历整个数组? MDN并未说明情况。

作为一个具体示例,此代码段需要5秒钟才能打印出done

var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');

1 个答案:

答案 0 :(得分:3)

Javascript“数组”(带有Array原型的不是数组)只是对象,因此这是

var m = [];
m[268435461] = -1;

完全相同
var m = {
    "268435461": -1
}

除了在第一种情况下,m具有Array原型和特殊的length属性。

但是,Array.prototype中定义的方法(如forEachjoin)正试图隐藏该事实并“模拟”顺序数组,就像其他语言中存在的那样。迭代其“ 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引擎并不愚蠢,并且完全按照上述方法实现迭代。他们确实有特定于阵列的优化,但是这些优化针对的是“良好”的顺序阵列,而不是针对像这样的奇怪边缘情况。底线:不要那样做!