我们在上集中,封装extend()
函数时知道,当对象类型的属性被拷贝时,实际上拷贝的只是该对象在内存中的位置指针,这一过程我们陈之为浅拷贝。在这种情况下,如果我们修改了拷贝对象,就等同于修改了原对象。那么,与之想对的,当然就是所谓的深拷贝了。
本集重点内容,就是带领诸君,如何实现浅拷贝和深拷贝。做一件事情的时候,最佳时间就是现在。因此,姑且就称之为最佳实践吧。
实践出真知,没有实践知识就不会变成真知,久而久之,就变成了一个空谈主义者了。
通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界和客观世界。实践、认识、再实践、再认识,这种形式,循环往复以至无穷,而实践和认识之每一循环的内容,都比较地进到了高一级的程度。这就是辩证唯物论的全部认识论,这就是辩证唯物论的知行统一观。
一、引用拷贝
事实上,对象类型
(包括数组
和函数
)通常都是以引用
形式进行传递的,这有时会导致一些不可预测的结果。
1.1 代码实现
function extend2(Child, Parent){
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p){
c[i] = p[i];
}
c.uber = p; //扩展子类原型,增加一个uber属性
}
var A = function(){};
var B = function(){};
A.prototype.stuff = [1,2,3];
A.prototype.name = "a";
extend2(B,A);
console.log(B.prototype.hasOwnProperty('name')); //true
console.log(B.prototype.hasOwnProperty('stuff')); //true
console.log(B.prototype.stuff === A.prototype.stuff); //true,说明了传递的是引用
B.prototype.stuff.push(4,5,6);
console.log(A.prototype.stuff); //[1,2,3,4,5,6],这里可以清楚的看到B对stuff操作影响了A
//当然如果我们用另一个对象对B的stuff属性进行完全重写(而不是修改现有属性)事情就不一样了
//在这种情况下,A的stuff会继续引用原有对象,而B的stuff属性指向了新的对象
B.prototype.stuff = ['a','b','c'];
console.log(A.prototype.stuff); //[1,2,3,4,5,6],这里可以看到A依然持有原来对象
1.2 代码分析
这段代码的主要思想是:当某些东西被创建为一个对象时,他们就被存储在内存中的某个物理位置上,相关变量和属性就会指向这些位置。而当我们将一个新的对象赋值给B.prototype.stuff
时,就相当于告诉它:“喂,忘了那个对象把,将指针转移到现在这个新的对象上来”。
二、浅拷贝(extendCopy)
2.1 思路分析
到目前为止,前面所有的示例都是以 构造器(构造函数) 创建对象为前提的,现在我们直接通过对象标识符来创建对象,这个时候该如何实现继承呢?
首先我们用var o = { }
语句创建一个对象作为画板,然后在逐步为其添加属性,但这次我们不用this
来实现,而是直接将现有对象的属性全部拷贝过来,例如下面:
function extendCopy(p){
var c = {};
for(var i in p){
c[i] = p[i];
}
c.uber = p;
return c;
}
2.2 功能测试
单纯属性的全拷贝是一种简单的模式,适用范围很广。下面来看这个函数的应用:
//shape对象
var shape = {
name: 'shape',
toString: function(){return this.name;}
}
//twoDee对象
var twoDee = extendCopy(shape);
twoDee.name = "2D shape";
twoDee.toString = function () {
return this.uber.toString() + "," + this.name;
}
//triangle对象
var triangle = extendCopy(twoDee);
triangle.name = "triangle";
triangle.getArea = function () {
return this.side * this.height / 2;
}
//手动赋值
triangle.side = 5;
triangle.height = 10;
//测试
console.log(triangle.getArea()); //25
console.log(triangle.toString()); //shape,2D shape,triangle
对于这种方法而言,可能的问题就在于初始化一个新的triangle
对象的过程过于繁琐。因为我们要手动为该对象的side
和height
属性进行赋值。但这个问题可以通过一个函数就能解决,例如我们可以创建一个init()
方法
三、深拷贝(deepCopy)
在某些场景下,我们希望拷贝一个对象的是深拷贝,而不是浅拷贝,那么我们该如何实现呢?
3.1 代码实现
- 核心思想:在于判断当前对象是否是基础属性,如果是就直接拷贝,如果不是进行递归循环拷贝
//深拷贝
function deepCopy(p, c){
var c = c || {};
for(var i in p){
if(typeof p[i] === 'object'){
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i],c[i]);
}else{
c[i] = p[i];
}
}
return c;
}
3.2 功能测试
var parent = {
numbers: [1,2,3],
letters: ['a','b','c'],
obj: {
prop: 1
},
bool: true
};
var mydeep = deepCopy(parent);
var myshallow = extendCopy(parent);
mydeep.numbers.push(4,5,6);
console.log(mydeep.numbers); //[1,2,3,4,5,6]
console.log(parent.numbers); //[1,2,3]
myshallow.numbers.push(10);
console.log(myshallow.numbers); //[1,2,3,10]
console.log(parent.numbers); //[1,2,3,10]
console.log(mydeep.numbers); //[1,2,3,4,5,6]