JavaScript深浅拷贝
https://juejin.im/post/5b00e85af265da0b7d0ba63f
所谓的浅拷贝:例如:
let appState = {
title: { text: 'React.js 小书', color: 'red', },
content: { text: 'React.js 小书内容', color: 'blue' }
}
let newAppState = {
...appState,
title: {
...appState.title,
text: '《React.js 小书》' } }
appState
和 newAppState
其实是两个不同的对象,因为对象浅复制的缘故,其实它们里面的属性 content
指向的是同一个对象;但是因为 title
被一个新的对象覆盖了,所以它们的 title
属性指向的对象是不同的。
appState
和 newAppState虽然是两个不同的对象,因为是浅拷贝所以当你改newappstate里的content里的值时appstate里的content也跟着变了,但是因为你newappstate里的title是新建的就把旧的替换了,当你改变title时旧的title是不影响的,所以浅拷贝一般就只拷贝了一层,里面深层的对象当你改变时还是会影响旧的对象里的相对的内容的
JavaScript深浅拷贝
基本类型 & 引用类型
ECMAScript中的数据类型可分为两种:
- 基本类型:undefined,null,Boolean,String,Number,Symbol
- 引用类型:Object,Array,Date,Function,RegExp等
不同类型的存储方式:
- 基本类型:基本类型值在内存中占据固定大小,保存在栈内存中
- 引用类型:引用类型的值是对象,保存在堆内存中,而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址
不同类型的复制方式:
- 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
let foo = 1;
let bar = foo;
console.log(foo === bar); // -> true
// 修改foo变量的值并不会影响bar变量的值
let foo = 233;
console.log(foo); // -> 233
console.log(bar); // -> 1
复制代码
- 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针,最终两个变量最终都指向同一个对象
let foo = {
name: 'leeper',
age: 20
}
let bar = foo;
console.log(foo === bar); // -> true
// 改变foo变量的值会影响bar变量的值
foo.age = 19;
console.log(foo); // -> {name: 'leeper', age: 19}
console.log(bar); // -> {name: 'leeper', age: 19}
复制代码
深拷贝 & 浅拷贝
- 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
总的来说,深浅拷贝的主要区别就是:复制的是引用还是复制的是实例
深浅拷贝的实现
看一看原生JavaScript中提供的一些复制方法究竟是深拷贝还是浅拷贝以及动手实现深拷贝。
浅拷贝
- Array.prototype.slice()
let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false
a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
复制代码
- Array.prototype.concat()
let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false
a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
复制代码
看起来Array的slice(),concat()似乎是深拷贝,再接着看就知道它们究竟是深拷贝还是浅拷贝:
let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false
a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
复制代码
同样,对于concat()也进行验证:
let a = [[1, 2], 3, 4];
let b = a.concat();
console.log(a === b); // -> false
a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
复制代码
综上, Array的slice和concat方法并不是真正的深拷贝,对于Array的第一层的元素是深拷贝,而Array的第二层 slice和concat方法是复制引用。所以,Array的slice和concat方法都是浅拷贝。
深拷贝
- JSON.parse()和JSON.stringify()
- JSON.stringify():把一个js对象序列化为一个JSON字符串
- JSON.parse():把JSON字符串反序列化为一个js对象
let obj = {
name: 'leeper',
age: 20,
friend: {
name: 'lee',
age: 19
}
};
let copyObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Sandman';
obj.friend.name = 'Jerry';
console.log(obj);
// -> {name: "Sandman", age: 20, friend: {age: 19,name: 'Jerry'}}
console.log(copyObj);
// -> {name: "leeper", age: 20, friend: {age: 19,name: 'lee'}}
复制代码
综上,JSON.parse()和JSON.stringify()是完全的深拷贝。
- 动手实现深拷贝 利用递归来实现对对象或数组的深拷贝。递归思路:对属性中所有引用类型的值进行遍历,直到是基本类型值为止。
function deepCopy(obj) {
if (!obj && typeof obj !== 'object') {
throw new Error('error arguments');
}
// const targetObj = obj.constructor === Array ? [] : {};
const targetObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
//只对对象自有属性进行拷贝
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
targetObj[key] = deepCopy(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}