深拷贝与浅拷贝在其它语言中也经常被提及到,在实际项目开发过程中也常常需要区分当前使用的到底是深拷贝还是浅拷贝,有时候在该使用深拷贝的地方,我们使用了浅拷贝,会导致深藏不露的bug。
在探讨深浅拷贝之前,我们先梳理一下js中的数据类型,js的数据类型分为两类:基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中。
如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是分别存储数据1的地址、数据2的地址和数据3的地址。
js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。
let a = 10;
let b = a;
a = 100;
console.log(a) // 100
console.log(b) // 10
修改其中一个变量的值,不会影响到另一个变量的值。
浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。
深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。
总结
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
let arr1 = [1, 42, [3, 4]]
let arr1Copy = arr1.slice()
arr1Copy[0] = 10 // 修改基本数据类型开辟新的内存地址
arr1Copy[2][0] = 100 // 修改引用数据类型指向同一块内存地址
console.log(arr1) // [1, 42, [100, 4]]
console.log(arr1Copy) // [10, 42, [100, 4]]
arr1中的元素1是基本数据类型,所以arr1Copy能够改变其值,而不影响arr1的值。
而[3, 4]是引用数据类型,arr1和arr1Copy指向同一块堆内存地址,所以这两个变量中3都变成了100。
let arr2 = ['cat', 'dog', 'pig', {'name': 'xia', 'age': 18}]
let arr2Copy = [].concat(arr2)
arr2Copy[2] = 'big pig'
arr2Copy[3]['name'] = 'aa'
console.log(arr2)
console.log(arr2Copy)
类似的还有…扩展运算符、Array.from、Object.assign()方法。
let a = {
name : '张三',
age : '18'
}
let b = { ...a };
b.name = '李四';
console.log('a:',a); // {name: '张三', age: '18'}
console.log('b:',b); // {name: '李四', age: '18'}
万能转换器 JSON.parse(JSON.stringify(obj))深拷贝已有对象,它可以深拷贝多层级的,不用担心嵌套问题。
let obj = {
name: 'wyc'
}
// 将对象序列化成json对象
let str = JSON.stringify(obj)
// 将json对象反序列化成js对象
let obj2 = JSON.parse(str)
// 修改拷贝的数据
obj2.name = 'wyc_ok'
console.log(obj); // {name: 'wyc'}
console.log(obj2); // {name: 'wyc_ok'}
但此拷贝的缺点,即没法拷贝内部函数
let a = {
name : '张三',
age : '18',
like(){
console.log('喜欢唱歌、滑冰');
}
}
let b =JSON.parse( JSON.stringify(a) );
b.name = '李四';
console.log('a:',a);
console.log('b:',b);
$.extend( 是否开启深拷贝, 拷贝后的值, 要拷贝的对象 )
let origin = [[1,1], 2, 3, 4];
let new_data = []
$.extend(true,new_data,origin)
new_data[0].push(5)
console.log('origin:',origin);
console.log('new_data:',new_data);
const origin = {
name : '张三',
age : '18',
like(){
console.log('喜欢唱歌、滑冰');
},
a : [[1,1], 2, 3, 4]
}
function extend(origin, deep){
// deep true 启动深拷贝
// false 浅拷贝
let obj = {}
// 数组对象
if(origin instanceof Array){
// true 数组 obj 就得是数组
obj = []
}
for(let key in origin){
let value = origin[key]
// 确定value是不是引用型,前提是deep 是true
obj[key] = (!!deep && typeof value === "object" && value !== null) ? extend(value, deep) : value
}
return obj
}
const new_data = extend(origin, true)
new_data.a[0].push(6666)
console.log(origin)
console.log(new_data)