魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 實作深拷貝函數

學習如何實作 JavaScript 深拷貝函數,處理複雜的嵌套物件與循環引用,掌握不同深拷貝方案的優缺點與實際應用。

影片縮圖

懶得看文章?那就來看影片吧

淺拷貝與深拷貝的區別

在 JavaScript 中,物件是引用類型,當我們將一個物件賦值給另一個變數時,實際上只是複製了對該物件的引用,而不是物件本身。這就是為什麼我們需要了解淺拷貝與深拷貝的區別。

淺拷貝 (Shallow Copy)

淺拷貝只複製物件的第一層屬性,對於嵌套的物件或陣列,仍然是複製其引用。

// 淺拷貝示例
const original = { a: 1, b: { c: 2 } };

// 使用 Object.assign 進行淺拷貝
const shallowCopy = Object.assign({}, original);

// 使用展開運算符進行淺拷貝
const spreadCopy = { ...original };

// 修改淺拷貝的嵌套屬性
shallowCopy.b.c = 3;

console.log(original.b.c); // 輸出: 3 (原物件也被修改了!)
console.log(spreadCopy.b.c); // 輸出: 3 (同樣被修改了!)

深拷貝 (Deep Copy)

深拷貝會遞迴地複製所有屬性,創建一個與原物件完全獨立的新物件,修改新物件不會影響原物件。

基本的深拷貝實作

下面是一個簡單的深拷貝函數實作,能夠處理基本的嵌套物件和陣列:

function deepClone(obj) {
  // 處理基本類型和 null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 創建新的物件或陣列
  const copy = Array.isArray(obj) ? [] : {};
  
  // 遞迴複製所有屬性
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  
  return copy;
}

// 使用範例
const original = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4, { e: 5 }]
  }
};

const cloned = deepClone(original);

// 修改克隆對象的嵌套屬性
cloned.b.c = 10;
cloned.b.d[2].e = 20;

console.log(original.b.c); // 輸出: 2 (原始值保持不變)
console.log(original.b.d[2].e); // 輸出: 5 (原始值保持不變)
console.log(cloned.b.c); // 輸出: 10
console.log(cloned.b.d[2].e); // 輸出: 20

這個基本實作能處理普通物件和陣列,但有幾個限制:

  • 1. 不能處理循環引用(會導致堆疊溢出)
  • 2. 不能正確處理 Date、RegExp、Map、Set 等特殊物件
  • 3. 不處理物件的原型鏈
  • 4. 不處理不可枚舉屬性