歡迎光臨
每天分享高質量文章

structuredClone():JavaScript中的物件深複製

展開語法是在 JavaScript 中複製物件的常用技術,你可以:

  • 展開一個數組字面量,從而複製一個數組
  • 展開一個物件字面量,從而複製一個物件

但是展開語法有一個顯著的缺點 —— 它的複製是一種淺複製,也就是說,只有頂層會被複制,但物件型別的屬性是共享的。

structuredClone() 是新推出的一個功能,很快就會得到大多數瀏覽器、Node.js 和 Deno 的支援。這個 api 可以建立物件的深層副本,也就是實現深複製。本文會解釋這個 api 是如何使用的。


本文的目錄如下:

在哪些 JavaScript 環境中可以使用 structuredClone()

儘管structuredClone()不是 ECMAScript 的一部分,但它已被新增到許多 JavaScript 環境中,並且仍然廣泛可用(現在或不久的將來):

注意:

  • structuredClone()在 WebWorkers 中並不總是可用 – 檢視MDN 瀏覽器相容性表以獲取更多資訊。
  • 在不支援的平臺上structuredClone,我們可以使用polyfill

透過展開語法複製物件是一種淺複製

在 JavaScript 中複製陣列和普通物件的一種常見方法是使用展開語法。比如下面這段程式碼:

const obj = {id: 'e1fd960b', values: ['a', 'b']};
const clone1 = {...obj};

但這種方式是一種淺複製。一方面,clone1.id只是一個副本,因此更改它不會更改obj

clone1.id = 'yes';
assert.equal(obj.id, 'e1fd960b');

另一方面,clone1.values中的陣列與 obj共享,如果我們改變它,我們也會改變obj

clone1.values.push('x');
assert.deepEqual(
  clone1, {id: 'yes', values: ['a', 'b', 'x']}
);
assert.deepEqual(
  obj, {id: 'e1fd960b', values: ['a', 'b', 'x']}
);

透過 structuredClone()深複製物件

結構化克隆具有以下型別簽名:

structuredClone(value: any): any

這個函式有第二個引數,但它很少有用,超出了本文的範圍,具體的你可以參閱 MDN 頁面structuredClone()

structuredClone() 可以深複製物件:

const obj = {id: 'e1fd960b', values: ['a', 'b']};
const clone2 = structuredClone(obj);

clone2.values.push('x');
assert.deepEqual(
  clone2, {id: 'e1fd960b', values: ['a', 'b', 'x']}
);
assert.deepEqual(
  obj, {id: 'e1fd960b', values: ['a', 'b']}
);

哪些值可以使用structuredClone()複製?

大多數內建值都可以複製

可以複製原始值:

> typeof structuredClone(true)
'boolean'
> typeof structuredClone(123)
'number'
> typeof structuredClone('abc')
'string'

大多數內建物件都可以複製 —— 即使它們有內部插槽:

> Array.isArray(structuredClone([]))
true
> structuredClone(/^a+$/) instanceof RegExp
true

但是,在複製正則表示式時,屬性.lastIndex始終重置為零。

某些內建值無法複製

一些內建物件不能被複制 —— 如果我們嘗試複製下面的物件,structuredClone()會丟擲一個DOMException

  • 函式(普通函式、箭頭函式、類、方法)
  • DOM 節點

前者的示範:

assert.throws(
  () => structuredClone(function () {}), // ordinary function
  DOMException
);
assert.throws(
  () => structuredClone(() => {}), // arrow function
  DOMException
);
assert.throws(
  () => structuredClone(class {}),
  DOMException
);

const objWithMethod = {
  myMethod() {},
};
assert.throws(
  () => structuredClone(objWithMethod.myMethod), // method
  DOMException
);
assert.throws(
  () => structuredClone(objWithMethod), // object with method
  DOMException
);

structuredClone() 丟擲的異常是什麼樣的?

try {
  structuredClone(() => {});
} catch (err) {
  assert.equal(
    err instanceof DOMException, true
  );
  assert.equal(
    err.name, 'DataCloneError'
  );
  assert.equal(
    err.code, DOMException.DATA_CLONE_ERR
  );
}

使用者定義類的例項變成普通物件

在下面的示例中,我們複製了 class 的一個例項C,但是,clone不是C 的例項。

class C {}
const clone = structuredClone(new C());

assert.equal(clone instanceof C, false);
assert.equal(
  Object.getPrototypeOf(clone),
  Object.prototype
);

總結一下 —— structuredClone() 永遠不會複製物件的原型鏈:

  • 內建物件的副本具有與原始物件相同的原型。
  • 使用者定義類的例項副本始終具有原型 Object.prototype(如普通物件)。

複製物件的屬性

structuredClone()並不總是會複製物件的屬性

  • 訪問器變成了資料屬性
  • 在副本中,屬性始終具有預設值

訪問器成為資料屬性

訪問器成為資料屬性:

const obj = Object.defineProperties(
  {},
  {
    accessor: {
      get: function () {
        return 123;
      },
      set: undefined,
      enumerable: true,
      configurable: true,
    },
  }
);
const copy = structuredClone(obj);
assert.deepEqual(
  Object.getOwnPropertyDescriptors(copy),
  {
    accessor: {
      value: 123,
      writable: true,
      enumerable: true,
      configurable: true,
    },
  }
);

屬性的副本具有預設屬性值

副本的資料屬性始終具有以下屬性:

writable: true,
enumerable: true,
configurable: true,
const obj = Object.defineProperties(
  {},
  {
    accessor: {
      get: function () {
        return 123;
      },
      set: undefined,
      enumerable: true,
      configurable: true,
    },
    readOnlyProp: {
      value: 'abc',
      writable: false,
      enumerable: true,
      configurable: true,
    },
  }
);
const copy = structuredClone(obj);
assert.deepEqual(
  Object.getOwnPropertyDescriptors(copy),
  {
    accessor: {
      value: 123,
      writable: true,
      enumerable: true,
      configurable: true,
    },
    readOnlyProp: {
      value: 'abc',
      writable: true,
      enumerable: true,
      configurable: true,
    }
  }
);
贊(0)

評論 搶沙發

  • 暱稱 (必填)
  • 郵箱 (必填)
  • 網址

分享創造快樂