JS实现多层深拷贝的简单方法

126 阅读2分钟

1.使用Json转换的方式

<script>
let a = [1,2,3,[4,5]]
let b = Json.parse(Json.stringify(a))
</script>

缺点:

  • 如果嵌套的是函数类型就不能用,会被忽略
  • undefined不能用,会被忽略
  • Symbol不能用,会被忽略
  • Date对象会变成字符串
  • 无法处理循环引用(会报错)

2.如果嵌套的有函数类型

<script>
let obj = {
name:"Tom",
age:18,
fn(){
console.log("age:",this.age)
},
tt:{project:"kkk"},
aa:[1,2,3]
}

let obj2 = {...obj} // 浅拷贝:只复制第一层属性值(如字符串、数字等),对于嵌套的对象或数组,复制的是引用地址
obj.fn = function(){
console.log("++age:",++this.age)}

obj.fn()
obj2.fn()

obj.tt.project = "yyy"
console.log("obj",obj.tt.project)
console.log("obj2",obj2.tt.project)

obj.aa[0] = 5
console.log("obj",obj.aa)
console.log("obj2",obj2.aa)

</script>

image.png

image.png

注意到当修改函数的值时,数据不会产生相互干扰,但是当修改数组或者对象的值会有干扰,因为数组或者对象复制过去的一层都是地址,函数是因为我们修改的方式相当于重写一个函数,因此你新的函数会在内存中开辟一个新的存储空间。

3.使用递归实现深拷贝

实现思路:

  1. 边界处理:如果是null或者非object类型,直接返回
  2. 处理循环引用:存储已拷贝过的对象,避免死循环
  3. 创建新容器:判断是数组还是对象,创建相应的空容器[]或者{}
  4. 递归拷贝:遍历原始对象的属性,递归调用deepClone复制给新容器
<script>
let a = [1,2,3]
console.log(typeof a) //object

let b=null
console.log(typeof b)//object
let c=undefined
console.log(typeof c)//undefined

/**
 * 深拷贝函数:递归实现
 * @param {any} oldData 需要拷贝的数据,可以是对象、数组或基本类型
 * @return {any} 返回拷贝后的新数据,与原数据无引用关系
 */
function deepClone(oldData) {
  // 判断当前处理的数据是否为对象类型,并且不是 null(因为 typeof null === 'object')
  if (typeof oldData === 'object' && oldData != null) {
    // 根据原始数据是数组还是对象创建一个新的空数组或空对象作为结果容器
    let res = Array.isArray(oldData) ? [] : {};

    // 遍历原始对象的所有自有属性(不包括继承来的属性)
    for (let k in oldData) {
      if (oldData.hasOwnProperty(k)) {
        // 对当前属性值递归调用 deepClone 函数进行深拷贝
        // 并将返回的结果赋值给结果对象/数组对应的键
        res[k] = deepClone(oldData[k]);
      }
    }

    // 返回已经完成深拷贝的对象/数组
    return res;
  } else {
    // 如果当前数据是基本类型(如 number, string, boolean)或者函数等,则直接返回原值
    // 因为基本类型在赋值时是值传递,不会产生引用问题
    return oldData;
  }
}

</script>

4.structuredClone()

const original = {a:1, b: {c:2}, d:new Date()};
const deepCopy = structuredClone(original)

deepCopy.b.c = 20; 

console.log(original.b.c)  //2
console.log(original.d.getTime() === deepCopy.d.getTime()) //true (Date被正确克隆了)

优点:

  • 内置的全局函数,无需引库
  • 专为深拷贝设计,性能好
  • 支持循环引用
  • 支持多种复杂类型,如Date、Map、Set、正则等

缺点:

  • 无法拷贝函数
  • 无法拷贝Error对象/DOM节点