var obj = {
name : "jack"
};
var copy_obj = obj; //copy_obj 是否就是我们所说的浅拷贝?
带着这个疑问,先查一下深浅拷贝的定义,所谓浅拷贝就是新增加一个“指针”指向已有的对象,深拷贝就是在浅拷贝的基础上,新开辟一个内存空间,使新增的“指针”指向新的内存空间保存的对象。类比下就是浅拷贝相当于起了个小名,深拷贝就是多莉羊。根据上面的叙述,似乎copy_obj 就是obj的浅拷贝。
暂时先这么理解着,接下来思考如何实现深拷贝。对,遍历对象啊。把key-value逐个取出来放到一个新对象中,我们先试一下:
var obj = {
name : 'jack',
age : 12,
score : [1,2,3]
};
var copy_obj = {};
(function copy(obj){
for(var k in obj){
if(obj.hasOwnProperty(k))
copy_obj[k] = obj[k];
}
})(obj);
这样看起来我们应该是得到两个对象了,下面做一下测试:
obj.age = 13;
copy_obj.age //12
obj.score.push(4);
copy_obj.score //[1,2,3,4]
也就是说,我们上面做的遍历赋值只是在 var copy_obj = obj 的基础上前进了很小的一步。再回顾一下:
var obj = {
name : "jack"
};
var copy_obj = obj;
obj.name = 'Alice';
copy_obj.name //Alice
所以,两者的区别在于,我们的function确实生成了一个新对象,但是新对象copy_obj中value为引用类型的值的指向和原对象obj中的指向是一致的。也就是说:
obj.score === copy_obj.score
由此,我们对之前关于赋值和浅拷贝的疑问有了一个答案,结果就是引用类型的赋值是浅拷贝中的一种。再去思考深拷贝的实现,上面写的function已经完成了创建一个新对象,缺少的就是让对象中的“子对象”也独立出来。思路有了,我们需要对“子对象”也进行遍历。因此,我们遇到了一个新问题,如何判断对象中value的类型。首先,我们看下Js内置的引用类型:
其中“基本包装类型”在我们这里是不用考虑的。在上一篇关于Js变量的文章里面我们提到了一个方法:
Object.prototype.toString.bind(obj)().slice(8, -1);
做个测试:
var obj = {
a : {},
b : [1,2],
c : new Date(),
d : /^a$/,
e : function(){
console.log('hello');
}
}
function valueType(obj){
var result = [];
for(var k in obj){
if(obj.hasOwnProperty(k)){
result.push(Object.prototype.toString.bind(obj[k])().slice(8, -1));
}
}
return result;
}
var result = valueType(obj); //["Object", "Array", "Date", "RegExp", "Function"]
OK,第一步解决。然后想一下对每种类型怎么处理,如果是Object,我可以新建一个var a = {},然后对内部再遍历;如果是Array,可以新建一个var b = [],然后再遍历放值;如果是Date,先看下obj.c是什么:
obj.c //Wed Apr 04 2018 10:41:22 GMT+0800 (中国标准时间)
既然是对象,那就有toString()方法,我们先让它以字符串形式返回,然后以其作为参数,再去创建一个Date,大概就是下面这样:
var str = obj.c.toString();
var newDate = new Date(str);
newDate //Wed Apr 04 2018 10:41:22 GMT+0800 (中国标准时间)
同样的,对于RegExp,是不是都能这样,尝试一下:
var reg = obj.d.valueOf();
var newReg = new RegExp(reg);
newReg // /^a$/
newReg == obj.d // false
没有问题,那Function呢,在js中,new Function(arg1,arg2,....,function_body),前面argx是函数参数,function_body是函数主体,并且都得是字符串形式,好像实现起来有点麻烦啊,强行去这么操作也是可以实现的,那就要拼接字符串了,暂且先不考虑它了吧(留下了没技术的眼泪。。。)
现在可以汇总一下了,把上面的内容整合一下,试着写一个深拷贝方法,直接贴代码:
var obj = {
a : {},
b : [1,2,new Date(),{},/^a$/],
c : new Date(),
d : /^a$/,
e : function(){
console.log('hello');
}
};
function deepClone(obj){
//拷贝对象
var copy_obj;
//判断类型
var type = Object.prototype.toString.bind(obj)().slice(8, -1);
//Object
if(type === 'Object'){
copy_obj = {};
}else if(type === 'Array'){
copy_obj = [];
}else if(type === 'Date'){
copy_obj = new Date(obj.valueOf());
}else if(type === 'RegExp'){
copy_obj = new RegExp(obj.valueOf());
}else{
//如果是基本类型值或者type === 'Function'
return obj;
}
//遍历
if(type === 'Object'){
for(var k in obj){
if(obj.hasOwnProperty(k)){
copy_obj[k] = deepClone(obj[k]);
}
}
}else if(type === 'Array'){
var i = 0,
len = obj.length;
for(;i
验证下得到结果:
obj.a == copy_obj.a //false
obj.b == copy_obj.b //false
obj.b[2] == copy_obj.b[2] //false
obj.b[3] == copy_obj.b[3] //false
obj.b[4] == copy_obj.b[4] //false
obj.c == copy_obj.c //false
obj.d == copy_obj.d //false
obj.e == copy_obj.e //true
和前面说的一样,只有Function没有完全独立出来。这个方法暂时就到这里。
我查找深拷贝资料的时候看到提及比较多的就是JSON反解析。具体方法如下:
var obj = {
a : {},
b : [1,2,new Date(),{},/^a$/],
c : new Date(),
d : /^a$/,
e : function(){
console.log('hello');
}
};
var copy_obj = JSON.parse(JSON.stringify(obj));
copy_obj //{a: {…}, b: Array(5), c: "2018-04-04T04:30:07.954Z", d: {…}}
结果是Chrome的给出来的,我们可以看到,JSON反解析存在以下几个问题:
我们看下过程,第一步把对象转换成JSON对象:
var obj = {
a : {},
b : [1,2,new Date(),{},/^a$/],
c : new Date(),
d : /^a$/,
e : function(){
console.log('hello');
}
};
JSON.stringify(obj) // "{"a":{},"b":[1,2,"2018-04-04T04:34:29.667Z",{},{}],"c":"2018-04-04T04:34:29.667Z","d":{}}"
可以看出来在这一步就已经出问题了,这也是用JSON做深拷贝的局限性,如果我们需要深拷贝的对象不包含Date,RegExp,Function,用JSON反解析就很方便了。反之还是一个一个检查类型做出相应的操作从而实现深拷贝。
关于深拷贝,还有一些其他方法,在这就不展开了,上面文字的叙述顺序就是我当时思考深拷贝这个问题时的大体思路。记录下来供大家参考,欢迎大家指正交流。