偶然与道友产生了深浅拷贝的异议,所以赶紧补一补习,需要的你也可以看看


浅拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原
对象。
举个栗子:假如有两个对象a和b,我想让b对象拥有a对象的属性和方法,然后b对象某些同属性的keyvalue和a不一样,可能会这么写

1
2
3
4
5
6
var a = {
name: "hhardyy"
}
var b = a;
b.name = "xiaofangkuai";
console.log(a.name) //xiaofangkuai

但是这样之后a对象的name也被改变了,举例浅拷贝一个initObj对象,里面有各种属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var initObj = {
a: "hhardyy",
b: {
a: "xiaofangkuai"
},
c: ["1", "2", "3"],
d: function () {
alert("hhardyy");
}
}

function clone(obj){
var newObj={};
for(key in obj){
newObj[key]=obj[key]
}
return newObj;
}
var newObj = clone(initObj);
console.log('拷贝的对象属性方法')
console.log(newObj.b);
console.log(newObj.c);
console.log(newObj.d);
newObj.a="xiaofangkuai";
newObj.b.a = "hhardyy";
newObj.c = [1, 2, 3];
newObj.d = function () {
alert("hhardyy1");
};
console.log('初始的对象属性方法')
console.log(initObj.b);
console.log(initObj.c);
console.log(initObj.d);
console.log('区别是initObj下面的b对象下面a没有被拷贝,也就是说只拷贝了一层')

console
console

ES6提供了一个新函数用于浅拷贝Object.assign(),可以处理一层的深度拷贝,可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。拷贝的是对象的属性的引用,而不是对象本身。
文档地址

1
Object.assign(target, ...sources)  //target=目标对象,sources=源对象,返回值等于目标对象

继续上面的例子,这是一层的

1
2
3
4
5
6
var a = {
name: "hhardyy"
}
var b = Object.assign({}, a);
b.name="xiaofangkuai"
console.log(a.name)//hhardyy,证明a.name没有被改动

继续上面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
}
}
var b = Object.assign({}, a);
b.name.firstname="hhardyy"
b.name.lastname.age=19
console.log(b.name)
console.log(a.name)

age出毛病了
age出毛病了

深拷贝

obj1要完全复制obj2的属性方法,同时obj1要是改变不会影响obj2的属性方法,同时满足多层拷贝

对象只有一层的话,可以用ES6的Object.assign(),例子还是上面那个

stringify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
}
}
var strA=JSON.stringify(a);
console.log('字符串的a对象:'+strA)
var b =JSON.parse(strA)
b.name.firstname="hhardyy"
b.name.lastname.age=19
console.log(b)
console.log(a)
a对象没有被改到
a对象没有被改到

这种深拷贝不用每层递归,也比较简单,但是这种方法也有不少坏处,IE6、7不兼容,兼容的方法也简单,勉强用evel或者去json的官网下载json类下面
的json2.js,然后引入,就可以兼容了。同时这种方法会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。也就是说
它会干掉原来对象里头的构造函数,所以有undefined和function会被除掉,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。还是那个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
},
func(){
console.log('hhardyy')
}
}
var strA=JSON.stringify(a);
var b =JSON.parse(strA)
console.log("console.log(typeof a.func):"+typeof a.func)
console.log("console.log(typeof b.func):"+typeof b.func)

b.func被干掉了
b.func被干掉了

stringify的缺点就是:无法实现对函数、RegExp等特殊对象的克隆,会抛弃对象的construct,所有的构造函数会指Object,有循环引用,会报错

递归拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
},
func(){
console.log('hhardyy')
}
}

var b={};

function deepClone(objA, objB) {
var obj = objB || {};
for (var i in objA) {
var prop = objA[i]; // 避免相互引用对象导致死循环
if(prop === obj) {
continue;
}

if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}

deepClone(a, b);
console.log(a);
console.log(b);
b.func被干掉了
b.func被干掉了

JQ的$.extend

1
2
3
4
5
6
7
8
9
10
11
12
13
var $ = require('jquery');
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
},
func(){
console.log('hhardyy')
}
}
var b = $.extend(true, {}, a);

函数库lodash的_.cloneDeep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var _ = require('lodash');
var $ = require('jquery');
var a = {
name: {
'firstname':'xiao',
'lastname':{
age:20
}
},
func(){
console.log('hhardyy')
}
}
var b = _.cloneDeep(a);

这个用起来简单,而且性能也不错

封装一个对象拷贝的方法

假如obj2要拷贝obj1里面的属性和方法可以用

1
2
3
4
5
6
function HexTend(obj1,obj2){
for(var attr in obj1){
obj2[attr]=obj1[attr];
}
return obj2;
}

也可以简单点b={…a}或者Object.assign(b,a),如果非对象参数出现在源对象的位置,那么这些参数都会转成对象,如果无法转成对象,就会跳过。也就是undefined和null不放
在第一个参数,就不会报错。其他类型的值比如number、string、boolean不在第一个参数,也不会报错。但是字符串会以数组形式拷贝入目标对象,其他值没效果。

注意:Object.assign方法实行的是浅拷贝,如果源对象某个属性的值是对象,则目标对象拷贝到的是这个对象的引用。