JS深拷贝和浅拷贝的核心区别就在于不同的数据类型在内存中存储的位置不同。
JS的数据类型分为
基本数据类型(值类型):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
引用数据类型(对象类型):对象(Object)、数组(Array)、函数(Function)。
基本类型数据保存在栈内存中;
引用类型数据保存在堆内存中,引用数据类型的变量是存放在栈中的,指向的是堆内存中实际对象的引用。
js深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。
浅拷贝:拷贝后,修改拷贝后的对象,会影响拷贝前的对象
深拷贝:拷贝后,修改拷贝后的对象,不影响之前的对象
浅拷贝
浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
可以使用 for in、 Object.assign、 扩展运算符 ... 、Array.prototype.slice()、Array.prototype.concat() 等,例如:
1、直接赋值
let a = [1,2,3,4];
let b = a;
b[3] = 9;
console.log(a); // [1, 2, 3, 9]
2、循环
let objA = {
name: '张三',
age: '18',
car: ['宝马','奔驰']
}
let objB = {}
for ( var key in objA) {
objB[key] = objA[key];
}
objA.car.push('特斯拉')
console.log(objB); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰', '特斯拉']}
3、Object.assign(target,source,source...)
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。注意,Object.assgin() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身
let objA = {
name: '张三',
age: '18',
car: ['宝马','奔驰']
}
let objB = Object.assign({}, objA);
console.log(objB); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰']}
objA.car.push('特斯拉')
console.log(objB); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰', '特斯拉']}
4、... 展开运算符
let objA = {
name: '张三',
age: '18',
car: ['宝马','奔驰']
}
let objB = { ...objA};
console.log(objB); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰']}
objA.car.push('特斯拉');
objB.name = '李四';
console.log(objB); // {'name': '李四', 'age': '18', 'car': ['宝马', '奔驰', '特斯拉']}
5、Array.prototype.concat()、Array.prototype.slice()
const arrA = [{name: '张三'}, {name: '李四'}]
const arrB = arrA.concat();
const arrC = arrA.slice(0,1);
arrA[0].name = '王二';
console.log(arrB); //[{name: '王二'}, {name: '李四'}]
console.log(arrC); // [{name: '王二'}]
浅拷贝只最第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。
深拷贝
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
1.深拷贝最简单的实现是: JSON.parse(JSON.stringify(obj))
let objA = {
name: '张三',
age: '18',
car: ['宝马','奔驰']
}
let objB = JSON.stringify(objA);
objA.car.push('特斯拉')
console.log(JSON.parse(objB)); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰']}
JSON.parse(JSON.stringify(obj)) 是最简单的实现方式,但是有一些缺陷:
对象的属性值是函数时,无法拷贝。
原型链上的属性无法拷贝
不能正确的处理 Date 类型的数据
不能处理 RegExp、Error 对象,否则会变成空对象;
会忽略 symbol
会忽略 undefined
不能存放 NaN、Infinity、-Infinity,否则会变成 null;
2、引入Lodash函数库
<script src='lodash.min.js'></script>
let objA = {
name: '张三',
age: '18',
car: ['宝马','奔驰']
}
let objB = _.cloneDeep(objA);
objA.car.push('特斯拉')
console.log(objB); // {'name': '张三', 'age': '18', 'car': ['宝马', '奔驰']}
3、实现一个 deepClone 函数
如果是基本数据类型,直接返回
如果是 RegExp 或者 Date 类型,返回对应类型
如果是复杂数据类型,递归。
考虑循环引用的问题
function deepClone(obj, hash = new WeakMap()) {
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
let t = new obj.constructor();
hash.set(obj, t);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
t[key] = deepClone(obj[key], hash);
}
}
return t;
}