1.首先要清楚,深拷贝和浅拷贝只是在引用数据类型(obj,Function,Array等)中才会有区别,基本数据类型不适用。
基本数据类型:Boolean Null Undefined Number String Symbol (ES6) BigInt (ES10)
引用数据类型:object、array、function
2.为什么不适用呢?这里涉及到栈和堆的存储问题。
我们知道,基本数据类型的值都是在栈中,而引用数据类型在栈中存储的的只是地址,而值是存储是在堆中;引用数据类型的值都是通过栈中的地址找到堆中对应的值。
3.浅拷贝 – 例子
let arr = [1,2,3];
let arr2 =[];
arr2 = arr;
arr[0] = 10;
console.log(arr2 ,arr2);
打印出来结果后发现arr2 随着arr的改变而改变了:
控制台打印结果都是:[10,2,3]
为什么两个数组之间的值会收到互相的干扰呢?
原理是这样:在引用数据类型中,一个引用数据把值赋给另一个引用数据类型,仅仅只是在栈中将引用的地址复制一遍,在栈中在开辟一个内存,存放一个相同的地址而已,但是并没有在堆中对应地开辟一块内存用来存放新的值。这就出现了两个地址指向同一块堆中的值,其实就是同一个值,无论是哪一有变动,两个取到的值都只是改变过后的最新的且是同样的值。当然,这在开发中肯定存在一定的问题,问了解决这个问题,我们不得不引进一个深拷贝的概念。
在介绍深拷贝之前,我先说说2个简单的实现浅拷贝的方法:
(1) Object.assign()
Object.assign我们经常会用到合并对象,当然利用Object.assign性质我们也可以实现对象的拷贝。
var obj1 = {a: 1, b: 2}
var obj2 = Object.assign({}, obj1)
obj2.a = 4
console.log(obj1, obj2)// {a: 1, b: 2} {a: 4, b: 2}
这里要注意的是Object.assign第一个参数必须是个空对象 这时候的浅拷贝才算是拷贝成功
(2) 解构赋值
var obj1 = {a: 1, b: 2}
var obj2 = {...obj1}
obj2.a = 4
console.log(obj1, obj2)
这里一样可以实现之前上面的结果。
但这两种拷贝有一个问题就是只能赋值一层,假设我们有如下数据结构
var obj1 = [{
name: '臧三',
childs: ['小明', '小芳']
}]
var obj2 = [...obj1]
obj2[0].childs = []
console.log(obj1, obj2)
打印出来可以看出obj1,obj2 的结果均同样变了,这并不是我们想要的结果,所以我们要用到深拷贝。
4.深拷贝 – 例子
原理:通过以下方法,拷贝的对象不会相互收到影响,深拷贝就是把某一个对象的地址和值都拷贝一份,分别在栈中和堆中重新开辟一块属于自己的内存,无论是哪一个值得改变都不会影响另一个的对象的值。
深拷贝的实现方法:
(1)递归去复制所有层级属性
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === 'object') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 判断obj子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === 'object') {
objClone[key] = deepClone(obj[key])
} else {
objClone[key] = obj[key]
}
}
}
}
return objClone
}
let a = [1,2,3,4]
let b = deepClone(a) // [1,2,3,4]
a[0] = 2 // [2,2,3,4]
b // [1,2,3,4]
(2)用JSON对象的parse和stringify(对函数不起作用)
function deepClone (obj) {
let _obj = JSON.stringify(obj)
let objClone = JSON.parse(_obj)
return objClone
}
let a = [0,1,[2,3],4]
let b = deepClone(a)
a[0] = 1
a[2][0] = 1 // [1,1,[1,3],4]
b // [0,1,[2,3],4]
(3)JQ的extend方法,extend方法也可以拷贝对象
$.extend([deep ], target, object1 [, objectN ])
//deep表示是否深拷贝,为true为深拷贝;为false,为浅拷贝。
//target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
//object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
let a = [0,1,[2,3],4]
let b = $.extend(true, [], a)
a[0] = 1
a[2][0] = 1 // [1,1,[1,3],4]
b // [0,1,[2,3],4]