目录
使用JSON.parse(JSON.stringify(obj))
一、为什么要区别深拷贝和浅拷贝
在JavaScript中,对象和数组是引用类型,这意味着当你将它们赋值给一个变量或者作为函数参数传递时,你实际上是在传递一个指向内存中对象的引用,而不是对象本身的副本。这就涉及到了深拷贝和浅拷贝的概念。
二、浅拷贝
2.1、什么是浅拷贝
浅拷贝只复制对象的第一层属性。也就是说,如果原始对象的属性是基本类型(如数字、字符串、布尔值),那么浅拷贝会复制这些值;但如果属性是引用类型(如数组、对象、函数),那么拷贝的只是引用,而不复制引用指向的内存地址中的数据。
2.2、浅拷贝的方法
使用Object.assign()
let original = { a: 1, b: { c: 2 } };
let shallowCopy = Object.assign({}, original);
original.a = 3;
original.b.c = 4;
console.log(shallowCopy.a) // 1 ; original.a是original的第一层属性,值是一个数字,所以浅拷贝到shallowCopy中还是一个数值,不受original影响
console.log(shallowCopy.b.c) // 4 ; original.b.c是original的第二层属性,第一层属性original.b的值是一个引用类型,浅拷贝只会拷贝引用,所以修改original.b.c也会修改shallowCopy.b.c
使用展开运算符(...)
let original = { a: 1, b: { c: 2 } };
let shallowCopy = { ...original }; // 使用展开运算符进行浅拷贝
original.a = 3;
original.b.c = 4;
console.log(shallowCopy.a) // 1 ; original.a是original的第一层属性,值是一个数字,所以浅拷贝到shallowCopy中还是一个数值,不受original影响
console.log(shallowCopy.b.c) // 4 ; original.b.c是original的第二层属性,第一层属性original.b的值是一个引用类型,浅拷贝只会拷贝引用,所以修改original.b.c也会修改shallowCopy.b.c
使用数组的slice()方法(仅适用于数组)
let originalArray = [1, 2, 3, [4, 5]];
let shallowCopyArray = originalArray.slice();
originArray.push(6);
originArray[3].push(5);
console.log(originalArray); // 输出: [1, 2, 3, [4, 5, 5], 6]
console.log(shallowCopyArray); // 输出: [1, 2, 3, [4, 5, 5]]
let originalArray = [1, 2, 3, [4, 5]];
let shallowCopyArray = originalArray.slice();
originArray.push(6);
originArray[3].push(5);
originArray[3] = [4, 5];
console.log(originalArray); // 输出: [1, 2, 3, [4, 5], 6]
console.log(shallowCopyArray); // 输出: [1, 2, 3, [4, 5, 5]] 因为 [4, 5]是一个新数组,是将新的引用赋给了originArray[3],并不会直接改变shallowCopyArray[3]的引用
2.3、关于赋值运算符(=)
在JavaScript中,赋值运算符(=)本身并不执行浅拷贝或深拷贝。赋值运算符只是将一个值或一个引用赋给一个变量。如果赋值的是一个基本数据类型(如数字、字符串、布尔值),那么赋值运算符会复制这个值。如果赋值的是一个引用类型(如对象、数组、函数),那么赋值运算符会复制这个引用,而不是引用指向的对象本身。
当使用赋值运算符来处理对象和数组时,没有创建一个新的对象/数组,两个变量指向的是同一个对象/数组,所以不管修改哪一个对象/数组的任何一层的任何一个数据,另外一个都会随之改变。(连浅层/第一层的数值都没有拷贝,完全依赖引用)
let a = 10; // a是基本数据类型
let b = a; // b复制了a的值,a和b现在是两个独立的数字
console.log(a === b); // true,因为它们都是数字10
let obj1 = { key: 'value' };
let obj2 = obj1; // obj2复制了obj1的引用
obj2.key = 'new value'; // 修改obj2的key属性
console.log(obj1.key); // 输出 'new value',因为obj1和obj2引用的是同一个对象
console.log(obj1 === obj2); // true,因为它们引用的是同一个对象
let obj3 = { key: 'value' , deep:{a:5 , b:6}};
let obj4 = obj1; // obj2复制了obj1的引用
obj3.deep.a = 6
console.log(obj4.deep.a); //6
三、深拷贝
3.1、什么是深拷贝
深拷贝会创建一个全新的对象,并且递归地复制所有属性,使得原始对象和拷贝对象完全独立,互不影响。深拷贝后的对象中所有的属性都是新的引用,即使属性是引用类型,也会被完全复制。
实现深拷贝的方法相对复杂,因为需要递归地复制对象的所有层级。以下是一些常见的实现方式
3.2、实现深拷贝的三种常见方法
使用JSON.parse(JSON.stringify(obj))
这是最简单的一个方法,实际开发中经常会用到,不过虽然简单但有局限性,不能处理函数、undefined、循环引用、特殊对象(如Date、RegExp)等。
let original = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(original));
使用第三方库,如lodash的cloneDeep()
let original = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(original));
手动实现递归函数
实现深拷贝的递归函数需要考虑多种情况,包括但不限于:
- 检测对象是否为基本类型。
- 处理循环引用。
- 复制数组和对象。
- 处理特殊对象(如Date、RegExp)。
举个例子:
function deepCopy(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
let result;
if (Array.isArray(obj)) {
result = [];
hash.set(obj, result);
for (let i = 0; i < obj.length; i++) {
result[i] = deepCopy(obj[i], hash);
}
} else {
result = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, result);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key], hash);
}
}
}
return result;
}
四、总结
浅拷贝适用于只复制对象的第一层属性,且这些属性不是引用类型。深拷贝适用于需要完全独立的副本,包括对象和数组的嵌套结构。选择哪种拷贝方式取决于你的具体需求和场景。
意犹未尽?更多精彩前端好文请关注:各种前端问题的技巧和解决方案
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~