关于 ArrayLike 的二三事

2012-11-29 by Dron

什么是 ArrayLike?

在浏览器中,经常会遇到一些具有部分数组特性的对象,如 Arguments、NodeList 等,这些对象是一组数据的有序集合,有长度、下标等特性。

它们不是真正意义的 Array,我们喜欢称作 ArrayLike 或「伪数组」,不能使用 Array 的原生方法来直接操作它们,但是可以跨原型链来对它们实施调用,如:

Array.prototype.slice.call( arguments, 0 );

构造 ArrayLike

除了上述对象外,还可以自己构造 ArrayLike。

对自定义的 JS 对象下标赋值,它就是一个 ArrayLike 了,但这样做还不够,如果用 Array 原型方法操作它,会被默认为是一个空数组:

var a = { slice: [].slice, push: [].push };

a[ 0 ] = "hello";
a[ 1 ] = "world";

console.log( a.slice().length ); // 期望得到 2,但结果为 0

a.push( "apple" );

console.log( a.length ); // 期望得到 3,但结果为 1
console.log( a[ 0 ], a[ 1 ] ); // 我们看到这里的 a[0] 已经被覆盖

解决这个问题的办法是给它加上 length 属性,对指定的范围起到保护作用:

var a = { push: [].push, length: 2 }; // 加了 length 属性

a[ 0 ] = "hello";
a[ 1 ] = "world";

console.log( a.length ); // 2

a.push( "apple" );

console.log( a.length ); // 3
console.log( a[ 0 ], a[ 1 ], a[ 2 ] ); // hello, world, apple

Console 对于 ArrayLike 的显示优化

经常用 jQuery 的同学可能已经发现,console.log( $( selector ) ) 会出来一个「数组」,里面有一堆 DOM 节点,其实 $() 得到的结果也是一个 ArrayLike,在控制台下以数组的形式显示不过是 jQuery 的一个小把戏而己,控制台会对结果做鸭子类型检测,如果检测结果是一个数组,控制台将会对它以数组的方式来显示。

那么检测的依据是什么呢?经过排除法得出,是 length 属性和 splice 属性,对比以下两个 console.log 在控制台下的显示:

var a = { length: 2 }, b = { length: 2, splice: [].splice };

a[ 0 ] = b[ 0 ] = "hello";
a[ 1 ] = b[ 1 ] = "world";

console.log( a );
console.log( b );

关于鸭子类型检测(Duck Typing)

如果某个动物长得像鸭子,它的叫声也像鸭子,那么我们认为它就是鸭子。这种判断方法称之为鸭子类型检测(Duck Typing)。

这种方法判断的结果并不够准确,在某些场景下已经足够用了。在 JavaScript 中,针对数组做 typeof 操作,得到的结果是 "object",为了正确判断数组,很多库也都采用了鸭子类型检测,控制台就是采用鸭子类型检测来判断数组,它的判断条件是:当对象的 length 属性是一个数字,并且 splice 属性是一个 function,那么该对象就是数组,所以我们会看到上面的对象 b,在控制台下以数组的方式显示出来。