javascript - 为什么在 JavaScript 中比较一个数组和一个数字这么慢?它在做什么?
问题描述
我不小心将一个大数组和一个数字与 进行比较<
,并且 JavaScript 锁定超过 5 秒。这种比较的预期行为是什么?它是否遍历整个数组?MDN没有说明情况。
作为一个具体的例子,这个代码片段需要超过 5 秒的时间来打印done
:
var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');
解决方案
Javascript“数组”(那些带有Array
原型,而不是类型数组)只是对象,因此这个
var m = [];
m[268435461] = -1;
完全一样
var m = {
"268435461": -1
}
除了在第一种情况下,m
具有Array
原型和特殊length
属性。
但是,在Array.prototype
(like forEach
or join
) 中定义的方法试图隐藏这一事实并“模拟”顺序数组,因为它们存在于其他语言中。当迭代他们的“this”数组时,这些方法会获取它的length
属性,将循环计数器从0
upto增加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 引擎有那么愚蠢,并且完全按照上面描述的方式实现迭代。他们确实有特定于数组的优化,但这些优化是针对“好的”顺序数组,而不是像这样奇怪的边缘情况。底线:不要那样做!
推荐阅读
- c++ - 如何将字符转换为整数
- function - 为什么有两个功能完全相同?为什么在 Kotlin 中必须以不同的方式调用?
- r - 使用 scale = "free_y" 时如何设置相等的面板水平空间?
- php - 包含 Unicode 字符的 DOMXPath 查询属性
- html - 页脚将自己定位在页面的左上角,我似乎无法让它下降
- mysql - AWS DMS MySQL 迁移陷入困境,“满载行”不断增加
- debugging - ESP8266:esptool 中的存根加载程序是什么意思?
- android - Android Espresso 测试授予权限
- r - R上三个子集的方差分析
- c++ - c++构造函数和复制构造函数