JS中对象深拷贝:structuredClone()

JS中对象深拷贝:structuredClone()

展开操作符(Spreading)是JavaScript中复制对象的常用手段:

展开数组字面量 复制一个数组

展开对象字面量 复制一个对象

展开有个明显的缺点,复制的结果是浅拷贝,顶层被复制,但属性值是引用数据。

structuredClone() 是一个新功能,现在已经被大多数浏览器、Node.js 和 Ddeeno 支持。它可以创建对象的深拷贝。下面是对它的研究分享,阅读时间约为 5 分钟。

本文目录:

  1. 在哪些 JavaScript 平台上可以使用structuredClone()?

  2. 通过展开复制对象的浅拷贝

  3. 使用structuredClone()深度复制对象

  4. StructuredClone() 可以复制哪些值?

4.1 大多数内置值都可以复制

4.2 某些内置值无法复制

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

4.4 复制对象的属性属性

  1. 参考资料

1. 在哪里可以使用structuredClone()?

尽管structuredClone()是 ECMAScript 的一部分,但它已被添加到许多平台中(并且会更快更广泛的普及):

Chrome 98

Safari 137 (Technology Preview Release)

Firefox 94

Node.js 17.0

Deno 1.14

提示:

● structuredClone()在 WebWorkers 中支持并不完全,具体可以查看MDN 浏览器兼容性表

● 在不支持的平台上structuredClone,可以使用polyfill代替。

2. 通过展开复制对象的浅拷贝

在 JavaScript 中复制数组和普通对象的一种常见方法是通过展开。比如展开对象代码:

ini 复制代码
const obj = {id: 'abcd1234', values: ['a', 'b']};
const clone1 = {...obj};

唉,这是使用频率非常高的浅拷贝方式。一方面,因为clone1.id是一个复制,因此更改它不会更改obj:

ini 复制代码
clone1.id = 'yes';
assert.equal(obj.id, 'abcd1234');

另一方面,数组 inclone1.values与 共享obj。如果我们改变它,同时也会改变obj:

php 复制代码
clone1.values.push('x');
assert.deepEqual(
  clone1, {id: 'yes', values: ['a', 'b', 'x']}
);
assert.deepEqual(
  obj, {id: 'abcd1234', values: ['a', 'b', 'x']}
);

3. 使用structuredClone()深度复制对象

arduino 复制代码
structuredClone(value: any): any

(这个函数有第二个参数 transferables,这个参数很少有用,超出了本文的范围。详细信息,请参考MDN 页面structuredClone()

structuredClone()深度复制对象:

php 复制代码
const obj = {id: 'abcd1234', values: ['a', 'b']};
const clone2 = structuredClone(obj);

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

4. 哪些值可以structuredClone()复制?

4.1 大多数内置值都可以复制

可以复制原始值:

java 复制代码
> typeof structuredClone(true)
'boolean'
> typeof structuredClone(123)
'number'
> typeof structuredClone('abc')
'string'

大多数内置对象都可以复制------即使它们有内部插槽:

javascript 复制代码
> Array.isArray(structuredClone([]))
true
> structuredClone(/^a+$/) instanceof RegExp
true

但是,在复制正则表达式时,属性.lastIndex始终重置为零。

4.2 某些内置值无法复制

一些内置对象不能被复制,structuredClone()会抛出一个:DOMException

● Functions (ordinary functions, arrow functions, classes, methods)

● DOM 节点DOM nodes

Functions示例:

scss 复制代码
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()抛出的异常是什么样的?

java 复制代码
try {
  structuredCljone(() => {});
} catch (err) {
  assert.equal(
    err instanceof DOMException, true
  );
  assert.equal(
    err.name, 'DataCloneError'
  );
  assert.equal(
    err.code, DOMException.DATA_CLONE_ERR
  );
}

4.3 自定义类的实例变成普通对象

在下面的示例中,我们复制了 class C的一个实例。结果不是实例而是普通对象。

javascript 复制代码
class C {}
const clone = structuredClone(new C());

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

总结一下------structuredClone()不会复制对象的原型链:

● 内置对象的副本具有与原始对象相同的原型。

● 自定义类的实例副本始终具有原型Object.prototype(如普通对象)。

4.4 复制对象的特性属性

structuredClone()并不能复制DOM节点特性属性

● 访问器变成了数据属性。

● 在副本中,特性属性始终具有默认值。

4.4.1 访问器成为数据属性

访问器成为数据属性:

php 复制代码
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,
    },
  }
);

4.4.2 属性的副本具有默认属性值

副本的数据属性始终具有以下属性:

vbnet 复制代码
writable: true,
enumerable: true,
configurable: true,
php 复制代码
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,
    }
  }
);

5. 参考资料

● WHATWG HTML 标准中的"Safe passing of structured data" 部分

● MDN"The structured clone algorithm"

● MDN" structuredClone()"

● 《Deep JavaScript》"Copying objects and Arrays"

● 《Deep JavaScript》"Copying instances of classes: "

● 《Deep JavaScript》"Property attributes: an introduction"

相关推荐
长风清留扬11 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_7482478025 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特2 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O4 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.10 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖11 小时前
[react]searchParams转普通对象
开发语言·前端·javascript