如何实现一个深拷贝函数来确保对象的完整复制?

来源:PHP编程网作者:长沙GEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何实现一个深拷贝函数来确保对象的完整复制?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何实现一个深拷贝函数来确保对象的完整复制?》有用,将其分享出去将是对创作者最好的鼓励。

深拷贝是指创建一个全新的对象,这个对象的所有层级属性都是原对象属性的独立副本,修改拷贝后的对象不会对原对象产生任何影响,和浅拷贝只复制第一层属性不同,深拷贝需要处理对象的所有嵌套结构。

如何实现一个深拷贝函数来确保对象的完整复制?

深拷贝的核心实现思路

实现深拷贝的核心逻辑是递归遍历原对象的所有属性,针对不同类型的属性做不同的处理:

  • 如果是基础数据类型,直接返回该值即可,因为基础类型本身是按值存储的,不存在引用问题
  • 如果是引用类型,需要判断是数组还是普通对象,创建对应的空容器,然后递归遍历其属性进行赋值
  • 如果遇到循环引用的情况,需要用一个映射表记录已经拷贝过的对象,避免递归进入无限循环

基础版深拷贝实现

先实现一个不考虑循环引用的基础版深拷贝函数,能够处理普通对象和数组:

function deepClone(target) {
  // 判断是否是对象类型,基础类型直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  // 判断是数组还是对象,创建对应的初始容器
  const result = Array.isArray(target) ? [] : {};
  // 遍历原对象的所有自有属性
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      // 递归处理每个属性的值
      result[key] = deepClone(target[key]);
    }
  }
  return result;
}

这个函数可以处理大部分普通场景,比如拷贝普通对象、数组,但是存在一个问题,如果对象存在循环引用,会直接导致递归栈溢出。

处理循环引用的完整版深拷贝

要解决循环引用的问题,我们需要用一个WeakMap来记录已经拷贝过的对象,键是原对象,值是拷贝后的对象,每次递归前先检查映射表中是否已经有对应的拷贝结果:

function deepClone(target, cache = new WeakMap()) {
  // 基础类型直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  // 检查是否已经拷贝过该对象,避免循环引用
  if (cache.has(target)) {
    return cache.get(target);
  }
  // 创建对应的容器
  const result = Array.isArray(target) ? [] : {};
  // 先存入缓存,再递归处理,避免循环引用时找不到缓存
  cache.set(target, result);
  // 处理对象的自有属性
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      result[key] = deepClone(target[key], cache);
    }
  }
  // 处理数组的特殊属性,比如length已经自动处理,这里补充处理Symbol属性
  const symbolKeys = Object.getOwnPropertySymbols(target);
  for (let symKey of symbolKeys) {
    result[symKey] = deepClone(target[symKey], cache);
  }
  return result;
}

深拷贝的边界情况处理

上面的实现已经能处理大部分场景,但是如果需要拷贝特殊对象比如DateRegExpMapSet等,还需要额外增加类型判断:

function deepClone(target, cache = new WeakMap()) {
  // 基础类型直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  // 检查缓存
  if (cache.has(target)) {
    return cache.get(target);
  }
  // 处理特殊对象类型
  if (target instanceof Date) {
    return new Date(target.getTime());
  }
  if (target instanceof RegExp) {
    return new RegExp(target.source, target.flags);
  }
  if (target instanceof Map) {
    const map = new Map();
    cache.set(target, map);
    target.forEach((value, key) => {
      map.set(deepClone(key, cache), deepClone(value, cache));
    });
    return map;
  }
  if (target instanceof Set) {
    const set = new Set();
    cache.set(target, set);
    target.forEach(value => {
      set.add(deepClone(value, cache));
    });
    return set;
  }
  // 普通对象和数组的处理
  const result = Array.isArray(target) ? [] : {};
  cache.set(target, result);
  // 处理普通属性
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      result[key] = deepClone(target[key], cache);
    }
  }
  // 处理Symbol属性
  const symbolKeys = Object.getOwnPropertySymbols(target);
  for (let symKey of symbolKeys) {
    result[symKey] = deepClone(target[symKey], cache);
  }
  return result;
}

深拷贝实现注意事项

  • 使用WeakMap而不是普通对象来存储缓存,是因为WeakMap的键是弱引用,不会阻止垃圾回收,避免内存泄漏
  • 函数类型的属性一般不需要深拷贝,直接赋值即可,因为函数本身是可复用的,拷贝函数没有实际意义,如果需要拷贝函数,可以通过eval或者new Function实现,但是会损失闭包上下文
  • 如果对象包含不可枚举的属性,上面的实现无法拷贝,需要结合Object.getOwnPropertyNames来获取所有属性名再处理
  • 实际开发中如果不想自己实现,也可以使用成熟的库比如lodash的cloneDeep方法,其内部已经处理了各种边界情况

深拷贝JavaScript对象复制递归遍历修改时间:2026-06-25 16:51:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。