JavaScript拷贝详解

前言

拷贝是在编程中常见的操作,它用于创建一个对象或数组的副本,以便在不影响原始对象的情况下进行操作和修改。在 JavaScript 中,拷贝通常针对引用类型进行操作,包括对象和数组。

一、浅拷贝

浅拷贝是指创建一个新对象或数组,并将原始对象或数组的引用复制给新对象或数组。这意味着新对象和原始对象仍然共享相同的内部数据。当对原始对象进行修改时,新对象也会受到影响。

以下是几种常见的浅拷贝方法:

  1. Object.create(obj):使用 Object.create() 方法可以创建一个新对象,新对象的原型是指定的对象 obj
  2. Object.assign({}, obj):通过 Object.assign() 方法可以将一个或多个源对象的属性复制到目标对象中,并返回目标对象。
  3. [].concat(arr):使用数组的 concat() 方法可以将一个或多个数组合并为一个新数组。
  4. 数组解构:通过解构赋值语法可以快速创建一个数组的浅拷贝。
  5. arr.slice():数组的 slice() 方法返回一个新数组,包含原数组的浅拷贝。

需要注意的是,浅拷贝只能处理一层的数据,如果原始对象或数组中包含嵌套的对象或数组,则浅拷贝只会复制它们的引用。这意味着对于嵌套对象或数组的修改会影响到新对象或数组。

浅拷贝示例:

1. 使用 Object.create(obj) 进行浅拷贝:
javascript 复制代码
const obj = { a: [1, 2], b: { c: 3 } };
const newObj = Object.create(obj);

console.log(newObj); // 输出:{}
console.log(newObj.a); // 输出:[1, 2]
console.log(newObj.b); // 输出:{ c: 3 }

// 修改原始对象
obj.a.push(3);
obj.b.c = 4;
console.log(obj);      // 输出:{ a: [ 1, 2, 3 ], b: { c: 4 } }
console.log(newObj.a); // 输出:[1, 2, 3],新对象也受到影响
console.log(newObj.b); // 输出:{ c: 4 },新对象也受到影响

在上面的示例中,我们使用 Object.create(obj) 方法创建了一个新对象 newObj,并将原始对象 obj 的引用复制给了它。当我们修改原始对象 obj 的值时,浅拷贝对象 newObj 的值也随之发生了变化。这是因为浅拷贝只是复制了原始对象的引用,而没有创建新的内存空间来存储数据。

2. 使用 Object.assign({}, obj) 进行浅拷贝:
javascript 复制代码
const obj = { a: [1, 2], b: { c: 3 } };
const newObj = Object.assign({}, obj);

console.log(newObj); // 输出:{ a: [1, 2], b: { c: 3 } }

// 修改原始对象
obj.a.push(3);
obj.b.c = 4;
console.log(obj);      // 输出:{ a: [ 1, 2, 3 ], b: { c: 4 } }
console.log(newObj);   // 输出:{ a: [ 1, 2, 3 ], b: { c: 4 } }
console.log(newObj.a); // 输出:[1, 2, 3],新对象也受到影响
console.log(newObj.b); // 输出:{ c: 4 },新对象也受到影响

在这个示例中,我们使用 Object.assign() 方法将一个空对象和原始对象 obj 的属性进行合并,并返回一个新的对象 newObj。当我们修改原始对象 obj 的值时,浅拷贝对象 newObj 的值并会发生变化。这是因为浅拷贝复制了原始对象的一层属性,也复制嵌套对象的引用。

3. 使用数组的 concat() 方法进行浅拷贝:
javascript 复制代码
const arr = [[1, 2], { a: 3 }];
const newArr = [].concat(arr);

console.log(newArr); // 输出:[[1, 2], { a: 3 }]

// 修改原始数组
arr[0].push(3);
arr[1].a = 4;
console.log(arr);    // 输出:[[1, 2, 3], { a: 4 } ]
console.log(newArr); // 输出:[[1, 2, 3], { a: 4 }],新数组也受到影响

在这个示例中,我们使用数组的 concat() 方法创建了一个新数组 newArr,其中包含原始数组 arr 的所有元素。当我们修改原始数组 arr 的值时,浅拷贝数组 newArr 的值并会发生变化。这是因为浅拷贝复制数组内部引用类型的引用。

4. 使用数组解构进行浅拷贝:
javascript 复制代码
const arr = [[1, 2], { a: 3 }];
const [...newArr] = arr;

console.log(newArr); // 输出:[[1, 2], { a: 3 }]

// 修改原始数组
arr[0].push(3);
arr[1].a = 4;
console.log(arr);    // 输出:[[1, 2, 3], { a: 4 }]
console.log(newArr); // 输出:[[1, 2, 3], { a: 4 }],新数组也受到影响

在这个示例中,我们使用数组解构语法 [...arr] 创建了一个新数组 newArr,其中包含原始数组 arr 的所有元素。当我们修改原始数组 arr 的值时,浅拷贝数组 newArr 的值并不会发生变化。这是因为浅拷贝复制数组内部引用类型的引用。

5. 使用 arr.toReversed().reverse() 进行浅拷贝:
javascript 复制代码
const arr = [[1, 2], { a: 3 }];
const newArr = arr.toReversed().reverse();

console.log(newArr); // 输出:[{ a: 3 }, [1, 2]]
// 修改原始数组
arr[0].push(3);
arr[1].a = 4;

console.log(newArr); // 输出:[{ a: 4 }, [1, 2, 3]],新数组也受到影响

在这个示例中,toReversed()是新增的方法,需要更高版本的node才能使用,所以使用时可能会报错,

二、深拷贝

深拷贝是指创建一个与原始对象完全独立的新对象或数组,新对象拥有与原始对象相同的值,但内部数据不共享。这样,在对原始对象进行修改时,不会影响到新对象。

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

在 JavaScript 中,常用的深拷贝方法是使用 JSON.parse(JSON.stringify(obj))。该方法通过将对象序列化为 JSON 字符串,然后再解析成新的对象来实现深拷贝。但是需要注意的是,这种方法也存在一些缺陷:

  1. 无法处理特殊的数据类型:undefinedfunctionSymbol 会在序列化过程中丢失。
  2. 无法处理循环引用:如果原始对象中存在循环引用(即对象内部存在相互引用的情况),那么深拷贝会陷入无限循环,在处理大型对象时可能导致堆栈溢出。
2. cloneDeep()

为了解决深拷贝的问题,我们可以借助第三方库如 Lodash 的 cloneDeep() 方法,它能够处理上述的特殊数据类型和循环引用。

javascript 复制代码
const cloneObj = _.cloneDeep(obj);

使用第三方库可以更安全、可靠地进行深拷贝操作。

深拷贝示例:

  1. 使用 JSON.parse(JSON.stringify(obj)) 进行深拷贝:
javascript 复制代码
const obj = { name: 'Charlie', age: 35 };
const newObj = JSON.parse(JSON.stringify(obj));

console.log(newObj); // 输出: { name: 'Charlie', age: 35 }

// 修改原始对象
obj.age = 40;

console.log(obj); // 输出: { name: 'Charlie', age: 40 }
console.log(newObj); // 输出: { name: 'Charlie', age: 35 }

在这个示例中,我们使用 JSON.parse(JSON.stringify(obj)) 方法创建了一个新的对象 newObj,并将原始对象 obj 的所有属性值复制给了它。当我们修改原始对象 obj 的值时,深拷贝对象 newObj 的值并不会发生变化。这是因为深拷贝创建了新的内存空间来存储数据,并且递归地复制了所有嵌套的对象和数组。

需要注意的是,JSON.parse(JSON.stringify(obj)) 方法存在一些限制。例如,它无法处理特殊的数据类型(如 undefinedfunctionSymbol),并且在处理循环引用时可能会导致无限循环的问题。

  1. 使用第三方库 Lodash 的 cloneDeep() 进行深拷贝(需要先安装 Lodash):
javascript 复制代码
const _ = require('lodash');

const obj = { name: 'David', age: 40 };
const newObj = _.cloneDeep(obj);

console.log(newObj); // 输出: { name: 'David', age: 40 }

// 修改原始对象
obj.age = 45;

console.log(obj); // 输出: { name: 'David', age: 45 }
console.log(newObj); // 输出: { name: 'David', age: 40 }

在这个示例中,我们使用第三方库 Lodash 的 cloneDeep() 方法创建了一个新的对象 newObj,并将原始对象 obj 的所有属性值复制给了它。当我们修改原始对象 obj 的值时,深拷贝对象 newObj 的值并不会发生变化。这是因为 cloneDeep() 方法能够处理特殊的数据类型和循环引用,并且递归地复制了所有嵌套的对象和数组。

三、总结

浅拷贝和深拷贝都是在编程中常见的操作,用于创建对象或数组的副本。浅拷贝只复制引用,而深拷贝创建独立的副本。对于浅拷贝,我们可以使用 Object.assign()concat() 或解构赋值等方法。对于深拷贝,可以使用 JSON.parse(JSON.stringify(obj)) 或第三方库提供的深拷贝方法。在实际应用中,我们需要根据具体情况选择适合的拷贝方式,以确保数据的完整性和安全性。

相关推荐
black^sugar39 分钟前
纯前端实现更新检测
开发语言·前端·javascript
GISer_Jing2 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245522 小时前
吉利前端、AI面试
前端·面试·职场和发展
2401_857600952 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600952 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL2 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
小白学大数据2 小时前
如何使用Selenium处理JavaScript动态加载的内容?
大数据·javascript·爬虫·selenium·测试工具
2402_857583492 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
isolusion2 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp3 小时前
Spring-AOP
java·后端·spring·spring-aop