利用 structuredClone()在JavaScript中深度复制对象

626 阅读3分钟

structuredClone() 是一个新的函数,很快将被大多数浏览器、Node.js和Deno支持。它可以创建对象的深度拷贝。这篇博文解释了它是如何工作的。


structuredClone() 在哪些JavaScript平台上可用?

尽管structuredClone() 目前不是ECMAScript的一部分,但它被添加到了许多平台的特定部分,并且仍然可以广泛使用(现在或很快)。

此外,还有计划将此功能加入ECMAScript中

提示。

通过传播复制对象是浅层次的

在JavaScript中复制Arrays和普通对象的一种常见方式是通过spreading。这段代码演示了后者。

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为它展示的用例。欲了解更多信息,请参见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节点

前者的演示。

structuredClone() 抛出的异常是什么样子的?

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

用户定义的类的实例成为普通对象

在下面的例子中,我们复制了一个类的实例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,
    }
  }
);