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)) 或第三方库提供的深拷贝方法。在实际应用中,我们需要根据具体情况选择适合的拷贝方式,以确保数据的完整性和安全性。

相关推荐
Tandy12356_2 分钟前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
老华带你飞19 分钟前
公寓管理系统|SprinBoot+vue夕阳红公寓管理系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·spring boot·课程设计
小飞猪Jay20 分钟前
面试速通宝典——10
linux·服务器·c++·面试
程序员陆通1 小时前
Spring Boot RESTful API开发教程
spring boot·后端·restful
qbbmnnnnnn1 小时前
【WebGis开发 - Cesium】如何确保Cesium场景加载完毕
前端·javascript·vue.js·gis·cesium·webgis·三维可视化开发
数据分析螺丝钉1 小时前
力扣第240题“搜索二维矩阵 II”
经验分享·python·算法·leetcode·面试
无理 Java1 小时前
【技术详解】SpringMVC框架全面解析:从入门到精通(SpringMVC)
java·后端·spring·面试·mvc·框架·springmvc
f8979070702 小时前
layui动态表格出现 横竖间隔线
前端·javascript·layui
鱼跃鹰飞2 小时前
Leecode热题100-295.数据流中的中位数
java·服务器·开发语言·前端·算法·leetcode·面试
TANGLONG2222 小时前
【C语言】数据在内存中的存储(万字解析)
java·c语言·c++·python·考研·面试·蓝桥杯