深拷贝和浅拷贝 一文读懂

41 阅读3分钟

1.什么是浅拷贝,什么是深拷贝?

A: (结合内存模型回答,显得专业)
它们的核心区别在于对引用类型值的处理方式不同 [[3, 6, 7:

  • 浅拷贝:  只复制对象的第一层属性。对于基本数据类型,直接赋值;但对于引用类型(如对象、数组),它复制的是内存地址(引用) 。这意味着,新旧对象的引用类型属性会共享同一块内存,修改其中一个,另一个也会受到影响。
  • 深拷贝:  递归复制对象的所有层级。它会开辟全新的内存空间,存放一份完全独立的副本。新旧对象完全不共享引用,修改其中一个,绝对不会影响另一个。

2.JavaScript 中有哪些实现深拷贝和浅拷贝的方法?

浅拷贝的实现方式

  • 展开运算符 (...):  最常用,语法简洁。
  • Object.assign()  ES6 标准方法。
  • 数组方法:  slice()concat()map()filter()(仅限数组)。
  • 深拷贝的实现方式

JSON.parse(JSON.stringify(obj))  最简单粗暴的方法,但有局限性(面试必考坑点)。

  • 手写递归:  最能体现原理,但需要处理循环引用。
  • 第三方库:  如 Lodash 的 _.cloneDeep(),生产环境推荐使用。
  • structuredClone()  浏览器原生支持的深拷贝 API(较新的考点)

Q: 使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝有什么缺点?

A: (这是面试官最爱听的细节,一定要多说)
虽然这种方法简单,但它无法处理很多特殊的 JavaScript 对象和类型,主要缺点如下

  1. 丢失 undefined 和 Symbol  这两种类型的属性会被直接忽略。

  2. 无法处理函数:  函数会被忽略,导致属性丢失。

  3. 无法处理循环引用:  如果对象内部引用了自己(obj.a = obj),该方法会直接报错 TypeError

  4. 特殊对象变形:

    • Date 对象会被转换成字符串。
    • RegExpError 对象会变成空对象 {}
    • MapSetWeakMapWeakSet 等数据结构会变为空对象或丢失。
  5. NaNInfinity  会被转换为 null

Q: 请手写一个深拷贝函数,要求能处理循环引用。

A: (这是进阶加分项,建议写出带 WeakMap 的优化版)
这个问题通常分两步考察:

function deepClone(target, cache = new WeakMap()) {
    // 1. 基本类型或 null 直接返回
    if (typeof target !== "object" || target === null) {
        return target;
    }

    // 2. 解决循环引用 (核心逻辑)
    if (cache.has(target)) {
        return cache.get(target); // 如果已缓存,直接返回缓存结果
    }

    // 3. 区分数组和对象,创建容器
    let cloneTarget = Array.isArray(target) ? [] : {};
    cache.set(target, cloneTarget); // 将当前对象存入缓存

    // 4. 递归拷贝 (这里为了简洁用了 for...in,实际可考虑 Reflect.ownKeys 处理 Symbol)
    for (const key in target) {
        if (target.hasOwnProperty(key)) {
            cloneTarget[key] = deepClone(target[key], cache);
        }
    }

    return cloneTarget;
}

// 测试循环引用
const obj = { name: "Alice" };
obj.self = obj; 
const copy = deepClone(obj);
console.log(copy.self === copy); // true,且不会爆栈

为什么要用 WeakMap

  • Map 对键是强引用,即使外部对象销毁了,Map 里的引用还在,会导致内存泄漏
  • WeakMap 对键是弱引用,当外部对象销毁时,垃圾回收器可以回收它,不会造成内存泄漏

🤔 5. 场景应用题:如何选择?

Q: 在实际开发中,你会如何选择使用深拷贝还是浅拷贝?

A:

  • 优先使用浅拷贝:  在 React/Vue 的状态管理中,大多数情况下(如更新对象的一层属性)使用浅拷贝(展开运算符)即可,性能更好,也符合不可变数据的原则。

  • 使用深拷贝:

    • 当你需要对数据进行完全隔离的操作,且不希望影响原数据时(例如:数据快照、撤销/重做功能)。
    • 当对象结构极其复杂且包含大量嵌套时。
  • 生产环境建议:  除非数据结构非常简单,否则不建议自己造轮子,推荐使用成熟的工具库(如 Lodash)或原生 structuredClone,因为它们处理了大量边界情况