在 JavaScript 中,深拷貝(Deep Copy)和淺拷貝(Shallow Copy)是處理對象複製的兩種不同方式:

一、淺拷貝(Shallow Copy)

只複製對象的第一層屬性,如果屬性是引用類型,則複製的是引用地址。

實現方式:

// 1. 擴展運算符
const obj = { a: 1, b: { c: 2 } };
const shallowCopy1 = { ...obj };

// 2. Object.assign()
const shallowCopy2 = Object.assign({}, obj);

// 3. 數組的淺拷貝方法
const arr = [1, 2, { a: 3 }];
const shallowArr1 = arr.slice();
const shallowArr2 = arr.concat();
const shallowArr3 = [...arr];

// 4. Array.from()
const shallowArr4 = Array.from(arr);

問題:

const original = {
  name: "John",
  address: { city: "New York" }
};

const shallow = { ...original };
shallow.address.city = "London";

console.log(original.address.city); // "London"(原對象也被修改了)

二、深拷貝(Deep Copy)

完全複製對象及其嵌套對象,新舊對象互不影響。

實現方式:

1. JSON 方法(最簡單但有限制)

const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));

侷限性:

  • 不能複製函數
  • 不能複製 undefined
  • 不能複製 Symbol
  • 不能處理循環引用
  • 會丟失 Date、RegExp 等特殊對象的構造函數

2. 手寫深拷貝函數

function deepClone(obj, hash = new WeakMap()) {
  // 處理基本類型和 null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 處理 Date
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 處理 RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  
  // 處理數組
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item, hash));
  }
  
  // 處理普通對象
  const clonedObj = {};
  
  // 使用 WeakMap 解決循環引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  hash.set(obj, clonedObj);
  
  // 遞歸複製所有屬性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clonedObj[key] = deepClone(obj[key], hash);
    }
  }
  
  // 處理 Symbol 屬性
  const symbolKeys = Object.getOwnPropertySymbols(obj);
  for (let symKey of symbolKeys) {
    clonedObj[symKey] = deepClone(obj[symKey], hash);
  }
  
  return clonedObj;
}

// 使用示例
const obj = {
  name: "John",
  address: { city: "New York" },
  date: new Date(),
  arr: [1, 2, { a: 3 }],
  func: function() { console.log("hello"); },
  [Symbol('id')]: 123
};

const cloned = deepClone(obj);

3. 使用第三方庫

// lodash
const _ = require('lodash');
const cloned = _.cloneDeep(obj);

// jQuery
const cloned = jQuery.extend(true, {}, obj);

三、性能對比

  • 淺拷貝:性能最好,內存佔用少
  • JSON 方法:中等性能,不能處理所有類型
  • 遞歸深拷貝:性能較差,但功能最完整
  • 第三方庫:功能完整,但需要額外引入

四、選擇建議

  1. 使用淺拷貝的場景

    • 對象結構簡單,沒有嵌套對象
    • 只需要複製第一層數據
    • 性能要求高
  2. 使用深拷貝的場景

    • 對象有深層嵌套結構
    • 需要完全獨立的副本
    • 需要修改副本而不影響原對象

五、現代 API

ES2023 引入了 structuredClone()

// 瀏覽器原生支持的深拷貝
const cloned = structuredClone(obj);

支持:基本類型、對象、數組、Map、Set、Date、RegExp、ArrayBuffer 等 不支持:函數、DOM 節點、Error 對象

六、注意事項

// 1. 循環引用問題
const obj = { a: 1 };
obj.self = obj;
// JSON.stringify(obj) 會報錯

// 2. 原型鏈上的屬性
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {};

const john = new Person('John');
// 淺拷貝不會複製原型鏈上的方法

根據具體需求選擇合適的拷貝方式,對於日常使用,可以先考慮 structuredClone()(現代瀏覽器)或 lodash 的 _.cloneDeep()