来源: 奕玖科技 瘦死的猪 | 2023/3/13 11:37:12
在JavaScript中,有时候我们需要拷贝一个对象或数组,以便在处理数据时不影响原始数据,这时就需要进行
深拷贝
(deep copy)。深拷贝是指将对象或数组完整复制一份,即使原始对象或数组中有嵌套对象或数组,也会递归地复制下去。本文将介绍JavaScript中多种深拷贝的实现方式。
1.
递归实现深拷贝
递归实现深拷贝是一种常见的方法,它的实现原理是通过递归的方式遍历对象或数组中的每一个属性,当遇到嵌套对象或数组时,再递归进行深拷贝。下面是递归实现深拷贝的代码:
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
Const cloneObj = Array.isArray(obj) ? [] : {};
for (Let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
return cloneObj;
}
该方法首先判断传入的参数是否为对象或数组,如果不是则直接返回,如果是则根据类型创建一个新的对象或数组。然后遍历原始对象或数组中的每一个属性,如果是基本数据类型,则直接赋值到新的对象或数组中,如果是对象或数组,则递归进行深拷贝。最后返回新的对象或数组。
2. JSON.parse(JSON.stringify(obj))实现深拷贝
除了递归实现深拷贝外,还可以使用JSON.parse(JSON.stringify(obj))实现深拷贝。该方法的原理是先将对象或数组转化为JSON字符串,再将字符串转化为新的对象或数组,这样可以保证新的对象或数组与原始对象或数组没有任何引用关系。下面是JSON.parse(JSON.stringify(obj))实现深拷贝的代码:
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
该方法非常简单,只需要一行代码即可实现深拷贝。但需要注意的是,该方法有一些局限性,它不能拷贝一些特殊的数据类型,比如函数、RegExp、Date等。
3. 使用lodash库的_.cloneDeep方法实现深拷贝
lodash是一个JavaScript的实用工具库,提供了很多常用的函数,其中包括_.cloneDeep方法用于实现深拷贝。该方法的实现原理与递归实现深拷贝类似,但lodash库可以处理一些特殊的数据类型,比如函数、RegExp、Date等。下面是使用lodash库的_.cloneDeep方法实现深拷贝的代码:
const _ = require('lodash');
function deepClone(obj) {
return _.cloneDeep(obj);
}
该方法非常简单,只需要调用lodash库的_.cloneDeep方法即可实现深拷贝。需要注意的是,lodash库是一个比较大的库,如果只需要实现深拷贝,建议使用其他方法。
4. 利用Weak
Map
实现深拷贝
利用WeakMap实现深拷贝的方法比较高效,它可以处理对象中的循环引用,避免陷入死循环。下面是利用WeakMap实现深拷贝的代码:
function deepClone(obj, map = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) {
return obj;
if (map.has(obj)) {
return map.get(obj);
const cloneObj = Array.isArray(obj) ? [] : {};
map.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], map);
return cloneObj;
}
该方法首先判断传入的参数是否为对象或数组,如果不是则直接返回,如果是则根据类型创建一个新的对象或数组。然后利用WeakMap记录已经拷贝过的对象,避免出现循环引用。接着遍历原始对象或数组中的每一个属性,如果是基本数据类型,则直接赋值到新的对象或数组中,如果是对象或数组,则递归进行深拷贝,并将拷贝后的对象存储到WeakMap中。最后返回新的对象或数组。
5. 利用ES6中的Object.assign方法实现深拷贝
利用ES6中的Object.assign方法也可以实现深拷贝,该方法的实现原理是使用Object.assign方法将多个对象或数组合并成一个新的对象或数组。由于Object.assign方法只会复制对象或数组的第一层属性,所以需要递归地进行深拷贝。下面是利用ES6中的Object.assign方法实现深拷贝的代码:
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
const cloneObj = Array.isArray(obj) ? [] : {};
Object.assign(cloneObj, obj);
for (let key in cloneObj) {
if (cloneObj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(cloneObj[key]);
return cloneObj;
}
该方法首先判断传入的参数是否为对象或数组,如果不是则直接返回,如果是则根据类型创建一个新的对象或数组。然后利用WeakMap记录已经拷贝过的对象,避免出现循环引用。接着遍历原始对象或数组中的每一个属性,如果是基本数据类型,则直接赋值到新的对象或数组中,如果是对象或数组,则递归进行深拷贝,并将拷贝后的对象存储到WeakMap中。最后返回新的对象或数组。
5. 利用ES6中的Object.assign方法实现深拷贝
利用ES6中的Object.assign方法也可以实现深拷贝,该方法的实现原理是使用Object.assign方法将多个对象或数组合并成一个新的对象或数组。由于Object.assign方法只会复制对象或数组的第一层属性,所以需要递归地进行深拷贝。下面是利用ES6中的Object.assign方法实现深拷贝的代码:
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
const cloneObj = Array.isArray(obj) ? [] : {};
Object.assign(cloneObj, obj);
for (let key in cloneObj) {
if (cloneObj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(cloneObj[key]);
return cloneObj;
}
该方法首先判断传入的参数是否为对象或数组,如果不是则直接返回,如果是则根据类型创建一个新的对象或数组。然后使用Object.assign方法将原始对象或数组中的第一层属性复制到新的对象或数组中。接着遍历新的对象或数组中的每一个属性,如果是对象或数组
,则递归进行深拷贝,并将拷贝后的对象赋值到新的对象或数组中。最后返回新的对象或数组。
6. 利用JSON实现深拷贝
利用JSON实现深拷贝的方法非常简单,只需要将对象或数组序列化成JSON字符串,再将JSON字符串解析成对象或数组即可。需要注意的是,该方法只适用于对象或数组中只包含基本数据类型,不适用于函数、日期、正则表达式等特殊类型。下面是利用JSON实现深拷贝的代码:
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
该方法首先调用JSON.stringify方法将对象或数组序列化成JSON字符串,然后再调用JSON.parse方法将JSON字符串解析成对象或数组。需要注意的是,该方法在序列化和解析过程中会丢失特殊类型的信息,例如函数、日期、正则表达式等。如果需要保留特殊类型的信息,建议使用其他方法。
7. 利用自定义函数实现深拷贝
利用自定义函数实现深拷贝的方法可以根据具体的需求进行自定义,可以处理特殊类型的数据,并可以添加一些特殊的逻辑。下面是一个基本的自定义深拷贝函数的代码:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
let cloneObj;
if (obj instanceof Date) {
cloneObj = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
cloneObj = new RegExp(obj);
} else if (obj instanceof Function) {
cloneObj = function() {
return obj.Apply(this, arguments);
} else {
cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
return cloneObj;
}
该方法首先判断传入的参数是否为对象或数组,如果不是则直接返回,如果是则根据类型创建一个新的对象或数组。如果是日期类型,则创建一个新的日期对象并赋值,如果是正则表达式,则创建一个新的正则表达式对象并赋值,如果是函数,则创建一个新的函数对象并赋值。如果是对象或数组,则递归进行深拷贝,并将拷贝后的对象赋值到新的对象或数组中。最后返回新的对象或数组。
深拷贝的注意事项
虽然深拷贝的实现方法有很多种,但是在实际使用中需要注意一些细节问题:
1.避免陷入死循环
在进行深拷贝时,需要注意对象中的循环引用问题。如果一个对象中包含另一个对象的引用,并且这两个对象互相引用
,那么进行深拷贝时就会陷入死循环。为了避免这种情况的发生,可以使用一个Map对象来存储已经拷贝过的对象,以避免重复拷贝和死循环的问题。以下是一个利用Map实现深拷贝的代码:
function deepClone(obj, cache = new Map()) {
if (obj === null || typeof obj !== 'object') {
return obj;
if (cache.has(obj)) {
return cache.get(obj);
let cloneObj;
if (obj instanceof Date) {
cloneObj = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
cloneObj = new RegExp(obj);
} else if (obj instanceof Function) {
cloneObj = function() {
return obj.apply(this, arguments);
} else {
cloneObj = new obj.constructor();
cache.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], cache);
return cloneObj;
}
该方法在递归进行深拷贝时,使用一个Map对象来存储已经拷贝过的对象,以避免重复拷贝和死循环的问题。
2.无法拷贝
原型链
上的属性
在进行深拷贝时,需要注意无法拷贝原型链上的属性。如果需要拷贝原型链上的属性,可以使用Object.getOwnPropertyNames方法获取所有的属性名称,然后逐一进行拷贝。以下是一个利用Object.getOwnPropertyNames方法实现深拷贝的代码:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
let cloneObj = new obj.constructor();
Object.getOwnPropertyNames(obj).forEach(key => {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
return cloneObj;
}
该方法在拷贝对象时,使用Object.getOwnPropertyNames方法获取所有的属性名称,然后逐一进行拷贝。
3.不同的对象和数组拷贝方法可能存在差异
在进行深拷贝时,需要注意不同的对象和数组拷贝方法可能存在差异。例如,对于一些特殊类型的数据,如Symbol、Map、Set等,不同的拷贝方法可能存在差异。因此,在进行深拷贝时,需要根据具体的需求选择合适的拷贝方法。
总结
深拷贝是JavaScript中常用的一种数据拷贝方式,它可以将一个对象或数组完整地拷贝一份,并且不受原对象的影响。在实际开发中,我们需要根据具体的需求选择合适的深拷贝方法,以避免出现意外的问题。在进行深拷贝时,需要注意避免
循环引用和原型链上的属性拷贝等问题,可以使用JSON.stringify和eval方法、递归和Map对象等方式进行解决。同时,还需要注意不同的对象和数组拷贝方法可能存在差异,需要根据具体的需求选择合适的拷贝方法。在实际开发中,我们需要根据具体的情况选择不同的深拷贝方法,以确保代码的稳定性和可靠性。