深浅拷贝
浅拷贝与深拷贝
- JavaScript的两种变量类型
JavaScript变量的类型分为两种,基本类型和引用类型,
其中基本类型是指简单的数据段,有5种:undefined、null、Boolean、number和string。
引用类型是指可能有多个值构成的对象,一般为:object、array、function等。
- 为什么要先说变量类型呢,是因为基本类型是按值访问的,不会影响到其他数据,例如:
var a = '前端'
var b = a
a = '前端工程师'
console.log(b)//前端
//所以基本类型的值没有深浅拷贝的概念
- 而引用类型的值是按地址访问的,简单的赋值,实际上只是把地址复制了一遍,修改任意一个值会影响到另外一个,例如:
var a = [1,2,3,4]
var b = a
a[1] = '已修改'
console.log(b)//[1,'已修改',3,4]
/*
可以看到,数组a赋值给了数组b,JavaScript引擎只是将a的地址赋值给了b,它们指向同一个内存地址,
并没有开辟新的栈,当修改a的值,b也被影响了,这就是浅拷贝,很多时候我们并不希望这样。
*/
- 什么是深拷贝浅拷贝
所以浅拷贝和深拷贝的概括解释为:
- 对基本类型变量,浅拷贝是对值的拷贝,没有深拷贝的概念。
- 对引用类型来说,浅拷贝是对对象地址的拷贝,并没有开辟新的栈,复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,另一个对象的属性也会改变,而深拷贝则是开辟新的栈。
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
-
浅拷贝的实现方式:
- Object.assign();
Object.assign()方法可以把任意多个的原对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。 - 函数库lodash的_.clone方法
该函数库也有提供_.clone用来做Shallow Copy。 - 展开运算符...
展开运算符是一个es6的特性,它提供了一种非常方便的方式来执行浅拷贝,这与Object.assign()的功能相同。 - Array.prototype.concat()
- Array.prototype.slice()
- Object.assign();
-
深拷贝的实现方式
- JSON.parse(JSON.stringify())
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。 - 函数库lodash的_.cloneDeep方法
- jQuery.extend()方法
- 手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
- JSON.parse(JSON.stringify())
数组的浅拷贝
如果是数组,我们可以利用数组的一些方法比如:slice、concat返回一个新数组的特性来实现拷贝。
比如:
var arr = ['old', 1, true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = 'new';
console.log(arr);//['old', 1, true, null, undefined]
console.log(new_arr);//['new', 1, true, null, undefined]
用slice可以这样做:
var new_arr = arr.slice();
但是如果数组嵌套了对象或者数组的话,比如:
var arr = [{old:'old'},['old']];
var new_arr = arr.concat();
arr[0].old = 'new';
arr[1][0] = 'new';
console.log(arr);//[{old:'new'},['new']]
console.log(new_arr);//[{old:'new'},['new']]
我们会发现,无论是新数组还是旧数组都发生了变化,也就是说使用concat方法,克隆的并不彻底。
如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进了修改,两者都会发生变化。
我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
所以我们可以看出使用concat和slice是一种浅拷贝。、
数组的深拷贝
那如何深拷贝一个数组呢?这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:
var arr = ['old', 1, true, ['old1','old2'],{old: 1}];
var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);
是一个简单粗暴的好方法,就是有一个问题,不能拷贝函数,我们做个试验:
var arr = [function(){
console.log(a)
},{
b: function(){
console.log(b)
}
}];
var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);
我们会发现new_arr变成了:

浅拷贝的实现
以上三个方法concat、slice、JSON.stringify都算是技巧类,可以根据实际项目情况选择使用,接下来我们思考一下如何实现一个对象或者数组的浅拷贝。
想一想,好像很简单,遍历对象,然后把属性和属性值都放在一个新的对象不就好了~
嗯,就是这么简单,注意几个小点就可以了:
var shallowCopy = function(obj){
//只拷贝对象
if(typeof obj !== 'object') return;
//根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
//遍历obj,并且判断是obj的属性才拷贝
for(var key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];
}
}
return newObj;
}
深拷贝的实现
那如何实现一个深拷贝呢?说起来也很简单,我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数不就好了~
var deepCopy = function(obj){
if(typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for(var key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
性能问题
尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。
参考:https://github.com/mqyqingfeng/Blog/issues/32;
https://juejin.cn/post/6844904197595332622;
https://www.kancloud.cn/ljw789478944/interview/397319

浙公网安备 33010602011771号