一篇文章彻底搞懂JavaScript中深拷贝和浅拷贝

前言:

1.浅拷贝(Shallow Copy):浅拷贝只复制对象的第一层属性。对于原始类型(如字符串、数字、布尔值),浅拷贝会创建其副本,而对于对象或数组这样的复杂类型,浅拷贝只复制引用,而不复制其实际内容。这意味着被拷贝对象的子对象或数组仍然和原对象共享同一个引用。

简单点来说就是:基于原对象,拷贝得到一个新的对象,原对象中内容的修改会影响新对象

2.深拷贝(Deep Copy):深拷贝会递归地复制对象及其所有子对象,从而创建一个与原对象完全独立的副本。对于复杂的数据结构,深拷贝会确保新对象与原对象之间没有任何引用关系。因此,修改深拷贝后的对象不会影响原对象。

简单点来说:基于原对象,拷贝得到一个新的对象,原对象中内容的修改不会影响新对象

直接上最容易理解的代码

一、浅拷贝实现

1.Object.create(obj)

其中:修改newObj中的值originalObj 的值也发生改变。

let originalObj = {
  a: 1,
  b: { c: 2 }
};

// 使用 Object.create() 创建新对象
let newObj = Object.create(originalObj);

console.log(newObj.a);   // 1 (继承自 originalObj)
console.log(newObj.b.c); // 2 (继承自 originalObj)

// 修改 newObj 的 b 属性(引用类型),会影响 originalObj
newObj.b.c = 100;

console.log(newObj.b.c);       // 100 (修改了 newObj 也修改了 originalObj 中 b 的内容)
console.log(originalObj.b.c);  // 100 (原型对象也被影响)

// 添加新属性到 newObj
newObj.a = 500;
console.log(originalObj.a);    // 1 (原型对象中的原始类型属性不会被影响)
console.log(newObj.a);         // 500 (新属性直接添加到了 newObj 自身)

注意:虽然 Object.create() 并不直接用于浅拷贝,但通过共享引用的特性,我们可以看到修改对象中的引用类型(如嵌套对象)会导致原型对象也被影响,从而体现出类似浅拷贝的行为。

2.Object.assign({}, obj)

Object.assign() 的浅拷贝特性表现在:如果对象内部包含嵌套的引用类型(如对象或数组),这些嵌套对象只是复制了引用地址,而不会创建独立的副本。因此,修改浅拷贝中的嵌套对象属性时,原对象也会随之改变。

let originalObj = {
  a: 1,
  b: { c: 2 }
};

// 使用 Object.assign() 实现浅拷贝
let shallowCopy = Object.assign({}, originalObj);

console.log(shallowCopy);      // 输出: { a: 1, b: { c: 2 } }

// 修改浅拷贝后的对象的原始类型属性
shallowCopy.a = 100;
console.log(shallowCopy.a);    // 输出: 100
console.log(originalObj.a);    // 输出: 1 (原对象的 a 未受影响)

// 修改浅拷贝后的对象的引用类型属性
shallowCopy.b.c = 200;
console.log(shallowCopy.b.c);  // 输出: 200
console.log(originalObj.b.c);  // 输出: 200 (原对象的 b.c 被影响)

// 检查引用是否相同
console.log(shallowCopy.b === originalObj.b);  // true,b 引用相同

3.[ ].concat(arr) 

[].concat(arr) 是一种实现浅拷贝的方法,通常用于复制数组。它会创建一个新数组,并将原数组的第一层元素复制到新数组中。如果原数组中包含引用类型(如对象或子数组),则这些引用类型元素的引用地址会被复制,而不会复制它们的实际内容。

let originalArray = [1, 2, { a: 10, b: 20 }];

// 使用 [].concat() 进行浅拷贝
let shallowCopy = [].concat(originalArray);

// 浅拷贝后的数组和原数组内容相同
console.log(shallowCopy);  // 输出: [1, 2, { a: 10, b: 20 }]

// 修改浅拷贝后的数组中的原始类型元素
shallowCopy[0] = 100;
console.log(shallowCopy);  // 输出: [100, 2, { a: 10, b: 20 }]
console.log(originalArray);  // 输出: [1, 2, { a: 10, b: 20 }] (原数组未受影响)

// 修改浅拷贝后的数组中的引用类型元素
shallowCopy[2].a = 200;
console.log(shallowCopy);  // 输出: [100, 2, { a: 200, b: 20 }]
console.log(originalArray);  // 输出: [1, 2, { a: 200, b: 20 }] (原数组的对象元素也被修改)

// 检查引用是否相同
console.log(shallowCopy[2] === originalArray[2]);  // 输出: true (引用类型元素共享同一引用)

4.[...arr]展开运算符实现

它只复制原数组中的第一层元素,对于原始类型(如数字、字符串)会创建一个新的副本,但对于引用类型(如对象、子数组),只复制引用地址,不复制内容。

let originalArray = [1, 2, { a: 10, b: 20 }];

// 使用展开运算符 [...] 进行浅拷贝
let shallowCopy = [...originalArray];

console.log("原数组:", originalArray);          // 输出: [1, 2, { a: 10, b: 20 }]
console.log("浅拷贝数组:", shallowCopy);        // 输出: [1, 2, { a: 10, b: 20 }]

// 修改浅拷贝数组的原始类型元素
shallowCopy[0] = 100;
console.log("修改后的浅拷贝数组:", shallowCopy);  // 输出: [100, 2, { a: 10, b: 20 }]
console.log("修改后的原数组:", originalArray);   // 输出: [1, 2, { a: 10, b: 20 }] (原数组未受影响)

// 修改浅拷贝数组的引用类型元素
shallowCopy[2].a = 200;
console.log("修改后的浅拷贝数组:", shallowCopy);  // 输出: [100, 2, { a: 200, b: 20 }]
console.log("修改后的原数组:", originalArray);   // 输出: [1, 2, { a: 200, b: 20 }] (原数组被影响)

// 检查引用是否相同
console.log(shallowCopy[2] === originalArray[2]);  // true,共享同一对象引用

5.arr.slice()

原理同上

// 原始数组
const arr = [1, 2, 3,{a:"小小"}];

// 浅拷贝得到数组
const shallowCopy = arr.slice(0);

// 修改原数组的值
arr[1] = 20;         // 不会改变新对象
arr[3].a = '小红';   // 会改变新对象

// 输出结果
console.log('Shallow Copy Array:', shallowCopy); // 输出Shallow Copy Array: [ 1, 2, 3, { a: '小红' } ]

6.arr.reverse().reverse()

实际上是对原数组进行了两次反转操作,结果是将数组的顺序恢复到最初的顺序。

let originalArray = [1, 2, 3, 4, 5];  
let sameArray = originalArray.reverse().reverse();  
  
console.log(sameArray === originalArray); // 输出: true,表示它们是同一个数组的引用

二、深拷贝

1.JSON.parse(JSON.stringify(obj))

let originalObj = {
  a: 1,
  b: {
    c: 2
  }
};

// 使用 JSON.parse(JSON.stringify()) 进行深拷贝
let deepCopy = JSON.parse(JSON.stringify(originalObj));

// 修改深拷贝后的对象
deepCopy.b.c = 100;

console.log("原对象:", originalObj);         // 输出: { a: 1, b: { c: 2 } }
console.log("深拷贝对象:", deepCopy);         // 输出: { a: 1, b: { c: 100 } }

结论:JSON.parse(JSON.stringify(obj)) 是一种常用的深拷贝方法。它通过将对象转换为 JSON 字符串,然后再解析回 JavaScript 对象,从而创建一个完全独立的对象副本。与浅拷贝方法不同,它可以深度复制嵌套的引用类型数据(如对象或数组),从而确保修改副本时不会影响原始对象。

2.递归拷贝(手动实现)

function deepClone(obj) {
  if (typeof obj !== "object" || obj === null) return obj; // 处理原始类型或 null
  let copy = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]); // 递归拷贝每个属性
    }
  }
  return copy;
}

let obj = { a: 1, b: { c: 2 }, d: [3, 4] };
let deepCopy = deepClone(obj);

deepCopy.b.c = 100;
deepCopy.d[0] = 300;
console.log(obj.b.c);       // 输出: 2
console.log(obj.d[0]);      // 输出: 3
console.log(deepCopy.b.c);  // 输出: 100
console.log(deepCopy.d[0]); // 输出: 300

3.structuredClone()(现代 API)

JS官方推出的一种深拷贝方法structuredClone()

let obj = { a: 1, b: { c: 2 }, d: [3, 4], e: new Date() };
let deepCopy = structuredClone(obj);

deepCopy.b.c = 100;
deepCopy.d[0] = 300;
console.log(obj.b.c);       // 输出: 2
console.log(obj.d[0]);      // 输出: 3
console.log(deepCopy.b.c);  // 输出: 100
console.log(deepCopy.d[0]); // 输出: 300

优点:

  • 原生支持,性能较好。
  • 支持大多数数据类型,包括循环引用。

缺点:

  • 需要现代浏览器(支持性不够广泛)。
  • 某些老旧浏览器可能不支持。

 结论

总结一下就是:

浅拷贝方法:

  1. Object.create(x)
  2. Object.assign({}, a)
  3. [].concat(arr)
  4. [...arr]解构数组
  5. arr.slice()
  6. arr.toReversed().reverse()

深拷贝方法:

  1. JSON.parse(JSON.stringify(obj))
  2. 通过递归去处理数据
  3. structuredClone();

希望大家能通过本文对深浅拷贝有进一步的学习和认知。 欢迎多多评论和指导

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值