添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
来源: 奕玖科技 瘦死的猪 | 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对象等方式进行解决。同时,还需要注意不同的对象和数组拷贝方法可能存在差异,需要根据具体的需求选择合适的拷贝方法。在实际开发中,我们需要根据具体的情况选择不同的深拷贝方法,以确保代码的稳定性和可靠性。