JS中对象深拷贝:structuredClone()
展开操作符Spreading
是 JavaScript 中复制对象的常用手段:
- 展开数组字面量 复制一个数组
- 展开对象字面量 复制一个对象
展开有个明显的缺点,复制的结果是浅拷贝,顶层被复制,但属性值是引用数据。
structuredClone()
是一个新功能,现在已经被大多数浏览器、Node.js 和 Deno 支持。它可以创建对象的深拷贝。
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 中复制数组和普通对象的一种常见方法是通过展开。比如展开对象代码:
const obj = {id: 'abcd1234', values: ['a', 'b']};
const clone1 = {...obj};
唉,这是使用频率非常高的浅拷贝方式。一方面,因为 clone1.id 是一个复制,因此更改它不会更改 obj:
clone1.id = 'yes';
assert.equal(obj.id, 'abcd1234');
另一方面,数组 inclone1.values 与 共享 obj。如果我们改变它,同时也会改变 obj:
clone1.values.push('x');
assert.deepEqual(
clone1, {id: 'yes', values: ['a', 'b', 'x']}
);
assert.deepEqual(
obj, {id: 'abcd1234', values: ['a', 'b', 'x']}
);
3. 使用 structuredClone()深度复制对象
structuredClone(value: any): any
(这个函数有第二个参数 transferables,这个参数很少有用,超出了本文的范围。详细信息,请参考MDN 页面 structuredClone())
structuredClone()深度复制对象:
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 大多数内置值都可以复制
可以复制原始值:
> typeof structuredClone(true)
'boolean'
> typeof structuredClone(123)
'number'
> typeof structuredClone('abc')
'string'
大多数内置对象都可以复制——即使它们有内部插槽:
> 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 示例:
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 {
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 的一个实例。结果不是实例而是普通对象。
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 访问器成为数据属性
访问器成为数据属性:
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 属性的副本具有默认属性值
副本的数据属性始终具有以下属性:
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,
}
}
);
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”
原文出自https://blog.csdn.net/songjungang/article/details/125373543