进入面试题,简单了解深浅拷贝

135 阅读4分钟

在 JavaScript 开发中,深拷贝(Deep Copy)浅拷贝(Shallow Copy) 是处理对象和数组时必须掌握的核心概念。它们决定了新对象是否与原对象共享内存引用,直接影响程序的行为和数据安全。

一、什么是浅拷贝?什么是深拷贝?

1. 浅拷贝(Shallow Copy)

  • 定义:创建一个新对象,但只复制原对象第一层属性的值
  • 对于基本数据类型(numberstringboolean),复制的是值。
  • 对于引用类型(objectarray),复制的是内存地址(引用),新旧对象的嵌套对象仍指向同一块内存。
  • 结果:修改嵌套对象会影响原对象。

2. 深拷贝(Deep Copy)

  • 定义:创建一个新对象,并递归复制原对象的所有层级,包括所有嵌套对象和数组。
  • 新对象与原对象完全独立,互不影响。
  • 结果:修改新对象不会影响原对象。

二、使用 Object.assign() 实现浅拷贝

Object.assign(target, ...sources) 是 ES6 提供的方法,常用于对象合并和浅拷贝。

示例:浅拷贝

const original = {
  name: "Alice",
  age: 25,
  address: {
    city: "Beijing",
    zip: "100000"
  },
  hobbies: ["reading", "music"]
};

// 使用 Object.assign() 创建浅拷贝
const shallowCopy = Object.assign({}, original);

// 修改浅拷贝的嵌套对象
shallowCopy.address.city = "Shanghai";
shallowCopy.hobbies.push("travel");

console.log(original.address.city); // "Shanghai" ← 原对象也被修改了!
console.log(original.hobbies);      // ["reading", "music", "travel"] ← 原数组也被修改了!

原因分析

  • Object.assign() 只复制了 addresshobbies引用,没有复制它们指向的对象。
  • 所以 shallowCopy.addressoriginal.address 指向同一个对象,修改一个会影响另一个。

适用场景

  • 只需复制对象第一层属性。
  • 性能要求高,且确认不会修改嵌套结构。

三、使用 JSON.parse(JSON.stringify()) 实现深拷贝

这是一种“取巧”的深拷贝方法,利用 JSON 序列化和反序列化实现。

示例:深拷贝

const original = {
  name: "Bob",
  age: 30,
  address: {
    city: "Shanghai",
    zip: "200000"
  },
  hobbies: ["sports", "coding"]
};

// 使用 JSON 方法实现深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改深拷贝的嵌套对象
deepCopy.address.city = "Guangzhou";
deepCopy.hobbies.push("gaming");

console.log(original.address.city); // "Shanghai" ← 原对象未受影响 ✅
console.log(original.hobbies);      // ["sports", "coding"] ← 原数组未受影响 ✅

原理

  1. JSON.stringify(original):将对象序列化为 JSON 字符串,过程中会递归遍历所有可枚举属性。
  2. JSON.parse(...):将字符串反序列化为新对象,生成全新的对象结构。

由于序列化过程会“扁平化”对象,反序列化时重建,因此新旧对象完全独立。


四、JSON.parse(JSON.stringify()) 的局限性(重要!)

虽然这种方法简单有效,但存在严重限制,不能用于所有场景:

1. 无法处理函数、undefinedSymbol

const obj = {
  func: function() { console.log("hello"); },
  value: undefined,
  sym: Symbol("id")
};

const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.func);  // undefined
console.log(copied.value); // undefined(消失)
console.log(copied.sym);   // undefined

原因:JSON 格式不支持函数、undefinedSymbol


2. 无法处理 Date 对象

const obj = { date: new Date() };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.date);        // "2025-08-12T15:30:00.000Z" ← 字符串!
console.log(typeof copied.date); // "string"

问题Date 被转为字符串,不再是 Date 实例。


3. 无法处理 RegExpError 等特殊对象

const obj = { regex: /abc/i };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.regex); // {} ← 变成空对象!

4. 无法处理循环引用(Circular Reference)

const obj = { name: "test" };
obj.self = obj; // 循环引用

JSON.stringify(obj); // TypeError: Converting circular structure to JSON

5. 会忽略 MapSetWeakMapWeakSet

const obj = { map: new Map([["key", "value"]]) };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.map); // {} ← 变成普通对象

五、对比总结

方法类型是否深拷贝支持函数支持 Date支持循环引用性能
Object.assign()浅拷贝NOYesYes(引用)Yes
JSON.parse(JSON.stringify())深拷贝Yes(有限)NONO(变字符串)(报错)中等(序列化开销)

六、何时使用哪种方法?

场景推荐方法
复制简单对象,无嵌套引用Object.assign()
复制纯数据对象(POJO),无函数、Date、循环引用JSON.parse(JSON.stringify())
需要完整深拷贝(支持所有类型)使用专业库(如 lodash.cloneDeep)或手写递归深拷贝
高频操作,性能敏感Object.assign() 或结构赋值 {...obj}

七、推荐的深拷贝方案

方案 1:使用 Lodash

npm install lodash
import { cloneDeep } from 'lodash';

const deepCopy = cloneDeep(original); // 支持所有类型,包括循环引用

方案 2:手写递归深拷贝(简化版)

function deepClone(obj, visited = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  
  // 处理循环引用
  if (visited.has(obj)) return visited.get(obj);
  
  let clone;
  if (obj instanceof Date) {
    clone = new Date(obj);
  } else if (obj instanceof RegExp) {
    clone = new RegExp(obj);
  } else if (Array.isArray(obj)) {
    clone = obj.map(item => deepClone(item, visited));
  } else {
    clone = {};
    visited.set(obj, clone);
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        clone[key] = deepClone(obj[key], visited);
      }
    }
  }
  
  visited.set(obj, clone);
  return clone;
}

八、总结

  • Object.assign() 是浅拷贝:只复制第一层,嵌套对象仍共享引用。
  • JSON.parse(JSON.stringify()) 是“伪深拷贝”:能处理嵌套对象,但有诸多限制(不支持函数、Date、循环引用等)。
  • 选择拷贝方式时,必须考虑数据结构的复杂性
  • 生产环境推荐使用 lodash.cloneDeep 或其他成熟库,避免踩坑。

核心原则

  • 简单数据 → Object.assign()JSON 方法。
  • 复杂数据、不确定结构 → 专业深拷贝工具。
  • 永远不要对包含函数或循环引用的对象使用 JSON 方法!