有关JavaScript中浅拷贝、深拷贝的简单研究
其实有关对象拷贝的场景一般来说用的不是很多,在日常开发中关于对象的拷贝的这种情况很少。
以前在Java中遇到过深浅拷贝问题,其实在JavaScript也存在。
前一阵工作中,有如下场景。用EasyUI显示左右两个Treegrid(树形表格),用户可以选址左侧树上的一个节点添加到右侧树,节点下附属的所有节点也要一并复制过去。这其中就涉及到了对象的拷贝问题。
欢迎转载,转载请注明出处,谢谢~(作者:喝酒不骑马 Colton_Null)
什么是对象的深浅拷贝呢?
一、有关”=”号、浅拷贝和深拷贝概念理解
1.”=”号的引用
对于基本类型的”=”赋值在这里就不过多讨论了。
看如下代码
var objA = {a:1, b:2};// 定义一个对象objA
var objB = objA;// objB 引用 objA
objA.a = 10;// 更改objA中a属性的值
console.log(objA);
console.log(objB);
控制台中显示:
Object { a: 10, b: 2 } //objA
Object { a: 10, b: 2 }//objB
这是为什么呢?
如图所示。实际上var objA = {a:1, b:2};
意思就是在内存空间中开辟一个内存用于存放 {a:1, b:2}这个对象。而变量objA指向这个对象的地址,也就是对象{a:1, b:2}被变量objA引用了。
而var objB = objA;
实际上就是生命一个变量objB,指向了objA指向的地址。也就是说objA和objB指向的是同一内存块中的对象。
所以无论是objA.a = 10;
还是objB.a = 10;
都会对更改这个内存块中的属性值。
2.浅拷贝
浅拷贝,就是只拷贝一层对象的属性。而且,重点是,浅拷贝中对对象的拷贝也只是引用拷贝。不理解?请先往下看。
假设有一个浅拷贝方法shallowClone()
var objA = {a:1, b:{b1: 100,}};// 定义一个对象objA
var objB = shallowClone(objA);// 浅拷贝得到对象objB
objB.a = 10;// 更改objB中a属性的值
objB.b.b1 = 500;// 更改objB中b对象中b1属性的值
console.log(objA);
console.log(objB);
控制台输出:
Object { a: 1, b: {b1: 500} } //objA
Object { a: 10, b: {b1: 500} } //objB
这是为什么呢?直接看图
浅拷贝时,在内存空间中开辟出一个新的内存块,并被变量objB引用。其中基本类型的属性在内存块中有了新的地址,而对于对象类型的属性,实际上指向的还是objA中的地址。例如,obj中的b指向是还是objA中b的地址。
所以改变objB中a的值,不会影响objA中a的值。但是改变对象b中的属相的值则都会受影响。
3.深拷贝
那么深拷贝就比较厉害了。它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。也就是说,深拷贝是在内存空间上开辟一块新的内存块,完全拷贝出一份一模一样的对象。被拷贝的对象和原对象完全独立,不存在引用同一地址的问题。
假设有一个深拷贝方法deepClone()
var objA = {a:1, b:{b1: 100,}};// 定义一个对象objA
var objB = deepClone(objA);// 深拷贝得到对象objB
objB.a = 10;// 更改objB中a属性的值
objB.b.b1 = 500;// 更改objB中b对象中b1属性的值
console.log(objA);
console.log(objB);
控制台输出:
Object { a: 1, b: {b1: 100} } //objA
Object { a: 10, b: {b1: 500} } //objB
二、用jQuery.extend()方法实现深浅拷贝
有关深浅拷贝的方法,大多都是自己封装的,在网上也能搜到一些别人封装好的方法。
而jquery中的extend()方法也能实现深浅拷贝。所以有关深浅拷贝的代码没有统一的写法。
1.实现浅拷贝
var oldObj = {
a: 1,
b: 'test',
c: [{ca: '222'}, {cb: 123}],
d: ['1', '2'],
e: function () {
var num = 1;
console.log(num);
}
};
var newObj = {};
$.extend(newObj, oldObj); // 浅拷贝
2.实现深拷贝
在extend()方法的第一个参数上添加true即可。
var oldObj = {
a: 1,
b: 'test',
c: [{ca: '222'}, {cb: 123}],
d: ['1', '2'],
e: function () {
var num = 1;
console.log(num);
}
};
var newObj = {};
$.extend(true, newObj, oldObj);// 深拷贝
更多有关$.extend()的用法请参考官方文档,在这里就不多做展开了。
三、有关可序列化对象的深拷贝简易方法
原理:利用JSON.stringify()
将对象序列化,再用JSON.parse()
进行解析,就可以得到一个新的对象。
【这种方式基本满足大部分深拷贝的场景,而且也很好理解,在我不知道extend之前都是用这种方式深拷贝的。】
var oldObj = {
a: 1,
b: 'test',
c: [{ca: '222'}, {cb: 123}],
d: ['1', '2']
};
var newObj = JSON.parse(JSON.stringify(oldObj));
注意,要想使用这种方式,被拷贝的对象必须是可序列化的,如果有{a: function(){}}这种类型属性,则拷贝后,该属性会丢失。
例如:
var oldObj = {
a: 1,
b: 'test',
c: [{ca: '222'}, {cb: 123}],
d: ['1', '2'],
e: function () {
}
};
var newObj = JSON.parse(JSON.stringify(oldObj));
console.log(oldObj);
console.log(newObj);
控制台输出
Object { a: 1, b: "test", c: Array[2], d: Array[2], e: e() }
Object { a: 1, b: "test", c: Array[2], d: Array[2] }
e属性丢失
四、注意事项
如果对象中有循环引用时,上述方法都不可用。
var a = {};
var b = {};
a.b = b;
b.a = a;
$.extend(true,{},a)
这时候会报异常
Uncaught RangeError: Maximum call stack size exceeded(…)
对于循环引用问题,尚未做研究。(PS:这种场景也是少之又少,至今没遇到过需要这么循环引用的业务场景)