一、问题
在日常开发中,会有复制一个对象,当改变新的对象时,原来的对象也发生了变化的情况。我们不希望出现这样的问题,那么可以用浅拷贝或深拷贝来解决这种情况。让我们带着一些问题来理解浅拷贝和深拷贝:
1、什么是浅拷贝?什么是深拷贝?
2、浅拷贝和深拷贝的区别?
3、如何实现浅拷贝或深拷贝?
二、认识深拷贝和浅拷贝
先来看一下下面的代码:
var a = 1;
var b = a;
b = 2;
console.log(a, b); // 1 2
var obj1 = {
a: 1
};
var obj2 = obj1;
obj2.a = 2;
console.log(obj1.a, obj2.a); // 2 2
我们知道,JavaScript
中有两大数据类型:分别是值类型和对象类型。值类型是没有深拷贝和浅拷贝这个概念的,这个和它们存储方式有关。值类型的数据是存储在栈内存中,按值访问;而对象类型的数据是存储在堆内存中,按引用(地址、指针)访问。
对象类型的数据直接赋值后会共用一个引用,改变其中一个都会影响到另一个,深拷贝和浅拷贝就是为解决这类对象直接赋值后依然“连接”的问题。那么就可以回答第一个问题,什么是浅拷贝和深拷贝了。
浅拷贝:复制一层对象的属性,并不包括对象里面是引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。
深拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
第二个问题,它们的区别:首先深拷贝和浅拷贝只针对如Object
,Array
这样的复杂对象的。深拷贝和浅拷贝的“深浅”主要针对的是对象的“深度”,简单来说,浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。
三、浅拷贝的实现
1、Object.assign()
Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var obj1 = {
a: 1,
}
var obj2 = Object.assign({}, obj1);
obj2.a = 2;
console.log(obj1.a); // 1
2、通过展开运算符 ... 来实现
var obj1 = {
a: 1,
}
var obj2 = {...obj1};
obj2.a = 2;
console.log(obj1.a); // 1
3、简单手写实现
下面来简单的实现一下浅拷贝:
function copy(obj) {
var target = {};
for (key in obj) {
target[key] = obj[key];
}
return target;
}
var obj1 = {
a: 1,
b: {
c: '我就是我,颜色不一样的烟火!',
},
}
var obj2 = copy(obj1);
obj2.a = 2;
console.log(obj1.a); // 1
以上便是浅拷贝的简单实现,拷贝完成后更改obj2.a
的值,但是obj1.a
的值没有发生改变,说明实现了浅拷贝。
浅拷贝可以解决常见的问题,但是如果不常见呢,比如说对象里面还有子对象,那么用浅拷贝就不够彻底;比如:
var obj3 = copy(obj1);
obj3.b.c = '你被我改变了吧!哈哈';
console.log(obj1.b.c); // 你被我改变了吧!哈哈
上面代码中,拷贝完成后更改了obj3.b.c
,结果obj1.b.c
也随之改变,说明子对象b
依然存在共用同一个引用的现象,所以浅拷贝拷贝的并不彻底。这时候深拷贝就该上场了,它能用递归的思想继续深挖,直到最底层为止。
四、深拷贝的实现
1、JSON.parse(JSON.stringify(object))
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象
var obj1 = {
a: 1,
b: {
c: '我就是我,颜色不一样的烟火!',
d: undefined,
}
}
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = '我承认我有赌的成分!';
obj2.b.d = '来了,老弟!',
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!
console.log(obj1.b.d); // undefined
这个方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON
格式能表示的所有数据类型。但是这个方法有一定的局限性:对于undefined
、symbol
、正则表达式类型、函数类型等数据无法进行深拷贝(而且会丢失相应的值);它会抛弃对象的constructor
,即深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object
;无法正确处理对象中存在循环引用的情况。
2、jQuery的extend方法
var obj3 = $.extend(true, {}, obj1);
obj3.b.c = '你有没有见过一招从天而降的掌法?';
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!
第一个参数为true
则说明要做深拷贝的操作,为false
则是做浅拷贝的操作。
3、递归拷贝实现
下面用递归简单的实现一下深拷贝:
function deepCopy(obj) {
var target = isArray(obj) ? [] : {};
for (key in obj) {
var copy = obj[key];
if (isObject(copy) || isArray(copy)) {
target[key] = deepCopy(copy); // 核心代码
} else {
target[key] = copy;
}
}
return target;
}
function isObject(obj) {
return toString.call(obj) === '[object Object]';
}
function isArray(arr) {
return toString.call(arr) === '[object Array]';
}
var obj4 = deepCopy(obj1);
obj4.b.c = '我摊牌了!';
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!
深拷贝是拷贝得非常彻底的,可以做到真正意义上的杜绝共用一个引用的问题。