JS对象复制(深拷贝、浅拷贝)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
JS对象复制(深拷贝、浅拷贝)
如何在 JS 中复制对象
在本⽂中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个⽅⾯,介绍多种 JS 中复制对象的⽅法。
在开始之前,有⼀些基础知识值得⼀提:Javascript 中的对象只是对内存地址的引⽤。
创建引⽤的副本会导致2个引⽤指向同⼀个的内存地址。
var foo = {
a: "abc"
}
console.log(foo.a); // abc
var bar = foo;
console.log(bar.a); // abc
foo.a = "yo foo";
console.log(foo.a); // yo foo
console.log(bar.a); // yo foo
bar.a = "whatup bar?";
console.log(foo.a); // whatup bar?
console.log(bar.a); // whatup bar?
Js
如上所⽰,对 foo 和 bar 两个对象中的任⼀个做修改,另⼀个都会发⽣相应变化。
因此,在 JS 中复制对象要格外⼩⼼。
浅拷贝
如果对象⽐较简单、只具有值类型的属性,可以使⽤扩展运算符(spread)或Object.assign(...)
var obj = { foo: "foo", bar: "bar" };
var copy = { ...obj }; // Object {foo: "foo", bar: "bar"}
Js
var obj = { foo: "foo", bar: "bar" };
var copy = Object.assign({}, obj); // Object {foo: "foo", bar: "bar"}
Js
注:上述两种⽅法都可以将属性值从多个源对象复制到⽬标对象:
var obj1 = { foo: "foo" };
var obj2 = { bar: "bar" };
var copySpread = { ...obj1, ...obj2 }; // Object {foo: "foo", bar: "bar"}
var copyAssign = Object.assign({}, obj1, obj2); // Object {foo: "foo", bar: "bar"}
Js
不过,如果对象的属性值也是⼀个对象,那么⽤上述⽅法拷贝就会有问题了:那样做只是创建了⼀个对象属性值引⽤的副本(但共享的还是⼀个内存),和本⽂开篇第⼀段代码⽰例中var bar = foo;的效果⼀样:
var foo = { a: 0 , b: { c: 0 } };
var copy = { ...foo };
copy.a = 1;
copy.b.c = 2;
// 修改副本 copy 中的属性 c,原对象 foo 中的属性也跟着变化
console.dir(foo); // { a: 0, b: { c: 2 } }
console.dir(copy); // { a: 1, b: { c: 2 } }
Js
深拷贝(有注意事项)
深拷贝对象,⼀种解决⽅案是将对象序列化为字符串,然后再将其反序列化:
var obj = { a: 0, b: { c: 0 } };
var copy = JSON.parse(JSON.stringify(obj));
然⽽,此⽅法仅在原对象包含可序列化值类型且没有任何循环引⽤时才有效。
不可序列化值类型的⼀个例⼦是Date对象 - JSON.parse只能将其解析为字符串⽽⽆法解析回其原始的Date对象 :(。
复杂对象的深拷贝
对于更复杂的对象,可以使⽤新的由 HTML5 规范定义的算法。
不过,虽然这种⽅法⽀持的内容类型多于JSON.parse,但在实际运⽤中,仍局限于某些内置类型,如:Date,RegExp,Map,Set,Blob,FileList,ImageData,稀疏和类型化数组(typed Array)。
它还保留了克隆数据中的引⽤,⽀持循环和递归结构。
⽬前,没有直接的⽅法来调⽤结构化克隆算法,但是有⼀些较新的浏览器功能使⽤这种算法。
因此,有⼀些可⽤的深拷贝对象的⽅法。
:利⽤通信功能使⽤的序列化算法。
由于此功能是基于事件的,因此⽣成的克隆也是异步操作。
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();
Js
通过:history.pushState()和history.replaceState()创建⾥⾯第⼀个参数obj的结构化克隆!注意,虽然此⽅法是同步的,但操纵浏览器的历史记录并不是⼀项快速的操作,也需要⼀定的过程和时间,反复调⽤此⽅法可能会导致浏览器⽆响应。
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
通过:在创建新通知时,构造函数会创建其关联数据的结构化克隆。
请注意,它还会尝试向⽤户显⽰浏览器通知,但除⾮应⽤程序已申请显⽰通知的权限,否则将以静默⽅式失败。
在授予权限的情况下,通知会⽴即关闭。
const structuredClone = obj => {
const n = new Notification("", {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Js
NODE.JS 中的深拷贝
从 8.0.0 版开始,Node.js 提供了与结构化克隆兼容的。
请注意,在撰写本⽂时,此 API 已标记为实验性:
const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();
Js
对于低于 8.0.0 的版本或为了更稳定地实现克隆,可以使⽤ lodash 的⽅法,该⽅法也基于结构化克隆算法。
总结
总⽽⾔之,在 JS 中⽤哪种⽅式复制对象,在很⼤程度上取决于你要复制的对象的上下⽂和类型。
虽然 lodash 是通⽤深拷贝最安全的选择,但如果⾃⼰动⼿写,可能会有更具针对性、更⾼效的实现办法,以下是⼀个适⽤于深度克隆⽇期的简单⽰例:
function deepClone(obj) {
var copy;
// 如果 obj 是 null、undefined 或不是对象,直接返回 obj
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Function
if (obj instanceof Function) {
copy = function() {
return obj.apply(this, arguments);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj as type isn't supported " + );
}
Js
就个⼈⽽⾔,我期待能够在任何地⽅使⽤结构化克隆。