探秘数组的push方法
Time: 10-05-25 Comments: 4
今天看到下面这段有趣的代码(据说是出自jQuery的作者哦),让我对push方法的执行机制产生了兴趣,决定一探究竟。。。
var elems = {
length: 0,
add: function(elem) {
Array.prototype.push.call(this, elem);
}
};
一. 疑问
在上段代码中,通过elems.add添加东西后,elems对象会变成了啥样呢?为什么要指定length属性?我们来操作看看:
// 分别打印出初始状态和操作后的elems,方便对比 console.dir(elems); elems.add(1); elems.add(2); console.dir(elems);
结果如下图:
我们看到:元素被成功添加(各浏览器均如此),且elems对象的length属性值发生了改变,其值即为添加项的数量,为什么会这样???
二. 解惑
看来一切都和push方法有关,我们到MDC中看看push方法的相关说明,我们着重看看下面几点说明:
(1) This method can be called or applied to objects resembling arrays; 理解:类数组的对象可以通过call或apply方法来调用push方法. (这里的类数组对象,指的应该是拥有length属性的对象,不过函数和字符串是个例外,这个后面再讲)
(2) The return value of this method is the new length property of the object upon which the method was called; 理解:push方法的返回值,是调用对象在执行push操作后(即调用push方法的对象)的length属性值(执行push操作后,length属性值会被更新,为什么length会被更新,可以从明城的《Array.prototype 的泛型应用》一文的评论中找到答案). // 验证下看看:elems对象的length属性值,默认为0,按照说明,插入一个值后,elems.length的值应该为1 var elems = { length: 0, add: function(elem) { Array.prototype.push.call(this, elem); } }; elems.add('test'); alert(elems.length); // 浏览器给出的结果: IE 6,7, 8: 1 FF 3.6: 1 Chrome 4: 1 Safari 4: 1 Opera 10: 1 // O(∩_∩)O,验证通过!!!
(3) The push method relies on a length property to determine where to start inserting the given values;
理解:push方法通过对象的length属性来决定,在什么位置插入给定的值.
// 我们来验证一下:elems的length属性被改成了1,按照上面的说明,如果我调用一次elems.add方法后,elems.length应该是2
var elems = {
length: 1,
add: function(elem) {
Array.prototype.push.call(this, elem);
}
};
elems.add('test');
alert(elems.length);
// 浏览器给出的结果:
IE 6,7, 8: 2
FF 3.6: 2
Chrome 4: 2
Safari 4: 2
Opera 10: 2
// O(∩_∩)O,验证通过!!!
(4) If the length property cannot be converted into a number, the index used is 0;
理解:如果对象的length属性不能被转换成数字,那么插入值时,位置从0开始.
// 照常验证:我们将elems对象的length属性改为字符串,然后插入一个值,按照说明,elems.length的值应该为1
var elems = {
length: 'aa',
add: function(elem) {
Array.prototype.push.call(this, elem);
}
};
elems.add('test');
alert(elems.length);
// 浏览器给出的结果:
IE 6,7, 8: 1
FF 3.6: 1
Chrome 4: 1
Safari 4: 1
Opera 10: 1
// O(∩_∩)O,再次验证通过!!!
(5) This includes the possibility of length being nonexistent, in which case length will also be created;
理解:当调用push方法的对象没有length属性时,就创建一个length属性(MD,这里没有说明这个创建的length属性,默认值是多少,NND,不知道这会导致兼容性问题么。。。).
// 继续验证:去掉elems对象的length属性,然后插入一个值,按照说明,此时elems.length的值应该为1(开始祷告浏览器都兼容吧。。。)
var elems = {
add: function(elem) {
Array.prototype.push.call(this, elem);
}
};
elems.add('test');
alert(elems.length);
// 浏览器给出的结果:
IE 6,7: undefined
IE 8: 1
FF 3.6: 1
Chrome 4: 1
Safari 4: 1
Opera 10: 1
// ( ⊙o⊙ ),不幸被咱言中,由此可见,把话说清楚是多么的重要啊。。。
三. 例外
上面提到:函数和字符串在调用push方法时,有些例外,这是指它们在调用push方法后,其length属性值不会改变,MDC中也给出了相应的说明:
// 函数(函数的length属性): (1) length is external to a function, and indicates how many arguments the function expects, i.e. the number of formal parameters. 理解:函数对象的length属性,表示的是函数的形参个数,是个只读属性.(试想一下,对于一个给定的函数,它的形参数量还能被改变么?) // 字符串: (2) The only native, array-like objects are strings, although they are not suitable in applications of this method, as strings are immutable. 理解:对于一个给定的字符串,它的长度是固定的(除非你去改动它),所以其length属性也是只读的(试想一下,通过修改一个字符串的length属性值,你能改变字符串的长度吗?) // 我们还是验证一下: function testFunc(a, b) { // 初始length为2 // code } var str = 'test'; // 初始length为4 Array.prototype.push.call(testFunc, 1); Array.prototype.push.call(str, 1); alert('func length: ' + testFunc.length + ', str length: ' + str.length); // 浏览器给出的结果: IE 6,7, 8: "func length: 2, str length: 4" FF 3.6: "func length: 2, str length: 4" Chrome 4: "func length: 2, str length: 4" Safari 4: "func length: 2, str length: 4" Opera 10: 直接报JS错误,提示"Uncaught exception: TypeError: Array.prototype.push: failed to update length"(个人认为Opera的处理更合理,只读属性就不应该被修改)
现在你应该明白,本文开始的那个例子里面,为什么要加上length属性了吧(对,为了兼容性)。好了,哥累了,今天就写这么多,回家咯。。。

Comments
jumkey at 2010年05月26日 9:47 上午
学习了,JavaScript还是很深奥的啊
涵宇 at 2010年05月26日 1:50 下午
太帅了!感觉到哥的一气呵成了,佩服!
console.dir用的更帅,我又觅到好东西了!
vapour at 2010年05月26日 3:49 下午
通过这个这个模仿数组,然后对这个模仿数组进行扩展,而不影响原生的Array对象。
Author comment by Tcer at 2010年05月27日 8:43 下午
@vapour,不错的想法。。。