JavaScript中的类数组对象

7

在查看Closure库中dom.js源代码时,我发现了这个(在goog.dom.getElementsByTagNameAndClass_函数中):

if (opt_class) {
var arrayLike = {};
var len = 0;
for (var i = 0, el; el = els[i]; i++) {
  var className = el.className;
  // Check if className has a split function since SVG className does not.
  if (typeof className.split == 'function' &&
      goog.array.contains(className.split(' '), opt_class)) {
    arrayLike[len++] = el;
  }
}
arrayLike.length = len;
return arrayLike;
}

相比于普通数组,这样做有什么好处呢?

6个回答

3

代码作者使用空的JavaScript对象作为类似数组的对象的基础,即可以通过索引访问并具有长度属性的对象。

我能想到的原因有两个:

  1. 内存使用 - 如果数组由分配n个元素的实现支持,当它达到极限时,它会以某个因子增加其容量,因此浪费了capacity - length的内存。
  2. CPU时间 - 实现者选择插入速度而不是随机访问速度 - 返回值更可能按顺序迭代而不是随机访问,而在插入时调整数组会导致分配-复制-取消分配,这会产生CPU惩罚。

我打赌其他JavaScript库中会发现类似的代码,并且这是基于在不同浏览器中找到最适合的解决方案的基准测试结果。

在Justin的评论后编辑

经过进一步搜索,似乎类数组对象在JavaScript开发人员中很常见:请查看David Flanagan的《JavaScript权威指南》,它有一个关于类数组对象的子章节。此外,这些 人们 也提到了它们。
没有提到为什么应该优先选择类数组而不是数组对象。这可能是一个很好的SO问题。
因此,第三个选项可能是关键:符合JavaScript API规范。

容量并不一定会随着每个插入操作而增长。通常的优化方式是每当容器达到容量限制时,通过一个大于1的值来增加容量。我没有查看过实际引擎代码,但我敢打赌该优化方式已经存在。至于 CPU 时间,我的基准测试结果显示相反的情况,虽然仅适用于 FF 3.5.6。 - Justin Johnson
当然,你是对的,但我并没有暗示每次插入都会增加容量... - Zoran Regvart

2
在这种情况下,我猜测arrayLike[len++] = el是对actualArray.push(el)的一种优化。然而,在进行简单的基准测试后(下面提供了代码结果),实际上使用标准数组使用push方法以及相同的构建技术比使用此方法更慢。
结果(来自OS X 10.5.8,FF 3.5.6)*:
push construction:        199ms (fastest)
indexed construction:     209ms
associative construction: 258ms (slowest)

总之,为什么Closure在这种情况下使用关联数组是我无法理解的。可能有原因(例如,这种技术在Chrome中可能表现更好,或者更不容易产生疑问的是,这种技术在未来的JavaScript引擎版本中可能表现更好),但在这种情况下我没有看到充分的理由。
* 没有提供平均值,因为测试运行时间会有所变化,但结果始终相同。如果您感兴趣,可以自己进行测试。
基准代码:
var MAX = 100000, i = 0, 
    a1 = {}, a2 = [], a3 = [],
    value = "";

for ( i=0; i<1024; ++i ) {
    value += "a";
}

console.time("associative construction");
for ( i=0; i<MAX; ++i ) {
    a1[i] = value;
}
a1.length = i;
console.timeEnd("associative construction");

console.time("push construction");
for ( i=0; i<MAX; ++i ) {
    a2.push(value);
}
console.timeEnd("push construction");

console.time("indexed construction");
for ( i=0; i<MAX; ++i ) {
    a3[i] = value;
}
console.timeEnd("indexed construction");

value的大小和类型对于测试来说并不重要,因为JavaScript使用写时复制。为了让那些不熟悉JavaScript这个特性的读者信服,我们使用了一个较大(1kb)的value


2
每当进行基准测试时,请记得针对IE 6进行基准测试。许多奇怪的性能技巧可能是为了解决IE 6的一些可怕性能特征而存在的,同时在其他浏览器上提供的好处很少,甚至没有。 - Brian Campbell
确实,我知道这一点。但是,就像我说的那样,我现在使用的是Mac,无法在完整的浏览器范围内进行测试。 - Justin Johnson
没错,Brian。鉴于浏览器市场份额(有很多讨厌IE的人,但事实就是事实),优先考虑IE的性能是有道理的。此外,请确保使用Google的V8引擎运行此测试。 - Josh Stodola

2
我认为这个例子创建了一个类似于数组的对象而不是真正的数组,因为其他DOM方法也返回类似于数组的对象(NodeList)。
在API中始终使用“类似于数组的”可以强制开发人员避免使用特定于数组的方法(使用goog.array代替),因此当有人稍后决定将getElementsByTagNameAndClass调用更改为getElementByTagName时,就会有更少的陷阱。

0
goog.dom.getElementsByTagNameAndClass_

你正在处理HTML集合。arrayLike对象是节点列表的“快照”,而不是“实时”集合对象。它使得像索引数组一样使用起来很方便,并且在循环遍历其成员时,如果创建或删除节点,也不太容易引起复杂问题。

0

我没有看到任何区别,因为alert(typeof []);返回的是"object"。每当我看到这样的东西(特别是来自谷歌),我都必须假设它是为了提高性能。

它本质上是返回一个没有所有继承函数的数组,例如pushpop


我也曾这么想过。结果我们俩都错了。看看我的基准测试吧。 - Justin Johnson
实际上,上次我检查时,Google Closure根本没有进行性能优化,看起来更像是遗留代码。 - kangax

-3
据我所知,这并没有什么好处,因为实际上除了创建语法之外,“稀疏数组”和“常规数组”之间根本没有任何区别 - 所有的Javascript对象都是关联数组。

不完全正确。对象/关联数组没有length属性,这就是为什么在这种情况下Closure库要添加它的原因(例如:console.log({}.length, [].length) => "undefined 0")。 - Justin Johnson
此外,数组([])具有更广泛的方法集(请参见Array.prototype)。 - Nickolay
数组扩展了对象(关联数组);它添加了所有的数组方法,同时还跟踪长度。请注意,length 不会跟踪非整数键对象。在 PHP 中,它们完全相同。 - Ruan Mendes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接