深入理解`Array.prototype.push`及应用

377 阅读2分钟

一直以来见过好多的代码都应用到Array.prototype.push.call,一直模棱两可,不知甚解,最近再看JavaScript设计模式与开发实践,稍微对其有了一点深入的了解。

**引子:**一个对象未必只能使用它自身的方法,有什么办法能够是一个对象借用不属于它的方法呢?

答案就是使用 callapply都能达到这个需求:

var obj1 = {
    name: 'dylan'
}
var obj2 = {
    getName: function() {
        return this.name
    }
}
console.log(obj2.getName.call(obj1)) // 输出: dylan
// 浅析:obj2.getName.call(obj1) 把 obj2.getName 中的 this 指向了 obj1

同理,我们也常常让类数组对象去借用 Array.prototype 方法,这是 callapply 最常见的应用场景之一:

(function() {
    Array.prototype.push.call(arguments, 4); // arguments 借用 Array.prototype.push 方法
    console.log(arguments); // 输出:[1,2,3,4]
})(1, 2, 3)

浅析1:Array.prototype上的方法原本只能用来操作array对象,但是用call和apply可以把任意对象当做this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛华并得到更广的应用

浅析2:看看Array.prototype.pushV8引擎中内部实现:

functionArrayPush() {
    var n = TO_UNIT32(this.length); // 
    var m = %_ArgumentsLength(); // 逐个复制元素
    for(var i = 0; i < m; i++){ // 
        this[i + n ]= %_Arguments(i);
    }
    this.length = n + m; // 修改数组的 length
    return this.length;
}

从以上的源码看出,push操作只是简单的复制元素,只要对象具备以下两个特性,就可以使用 Array.prototype.push()

  • 1、可读写的length属性
  • 2、对象本身可以读取属性
// 举个例子
var obj = {};
[].push.call(obj, 'a');
console.log(obj); // {0: 'a', length: 1}

由此可见,对象不仅多了键值对0: 'a',还多了键值对length: 1,从源码可以看出,push方法复制元素使用的是指针this, 所以可以很清楚的了解到 push 是浅复制

// 再看一个例子
var array=[];
var item={};
for(var i=0;i<5;i++){
    item.a=i;
    array.push(item);
}
console.log(item); // {a: 4}
console.log(array);// [{a: 4},{a: 4},{a: 4},{a: 4},{a: 4},{a: 4}]

浅析:因为push为浅复制,复制的只是引用地址,若是item发生变化,则复制的值也会发生变化