Javascript中怎么实现对象的拷贝,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
在开始之前,我先普及一些基础知识。Javascript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以重新被赋值。所以仅仅复制这个指针,其结果是有两个指针指向内存中的同一个地址。
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?
通过上面的例子可以看到,对象 foo 和 bar 都能随着对方的变化而变化。所以在拷贝 Javascript 中的对象时,要根据实际情况做一些考虑。
如果要操作的对象拥有的属性都是值类型,那么可以使用扩展语法或 Object.assign(...)
var obj = { foo: "foo", bar: "bar" }; var copy = { ...obj }; // Object { foo: "foo", bar: "bar" } var obj = { foo: "foo", bar: "bar" }; var copy = Object.assign({}, obj); // Object { foo: "foo", bar: "bar" }
可以看到上面两种方法都可以把多个不同来源对象中的属性复制到一个目标对象中。
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" }
上面这种方法是存在问题的,如果对象的属性也是对象,那么实际被拷贝的只是那些指针,这跟执行 var bar = foo; 的效果是一样的,和第一段代码中的做法一样。
var foo = { a: 0 , b: { c: 0 } }; var copy = { ...foo }; copy.a = 1; copy.b.c = 2; console.dir(foo); // { a: 0, b: { c: 2 } } console.dir(copy); // { a: 1, b: { c: 2 } }
想要对一个对象进行深拷贝,一个可行的方法是先把对象序列化为字符串,然后再对它进行反序列化。
var obj = { a: 0, b: { c: 0 } }; var copy = JSON.parse(JSON.stringify(obj));
不幸的是,这个方法只在对象中包含可序列化值,同时没有循环引用的情况下适用。常见的不能被序列化的就是日期对象 —— 尽管它显示的是字符串化的 ISO 日期格式,但是 JSON.parse 只会把它解析成为一个字符串,而不是日期类型。
对于一些更复杂的场景,我们可以用 HTML5 提供的一个名为结构化克隆的新算法。不过,截至本文发布为止,有些内置类型仍然无法支持,但与 JSON.parse 相比较而言,它支持的类型要多的多:Date、RegExp、 Map、 Set、 Blob、 FileList、 ImageData、 sparse 和 typed Array。它还维护了克隆对象的引用,这使它可以支持循环引用结构的拷贝,而这些在前面所说的序列化中是不支持的。
目前还没有直接调用结构化克隆的方法,但是有些新的浏览器特性的底层用了这个算法。所以深拷贝对象可能需要依赖一系列的环境才能实现。
Via MessageChannels: 其原理是借用了通信中用到的序列化算法。由于它是基于事件的,所以这里的克隆也是一个异步操作。
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() }; originaloriginal.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();
Via the history API:history.pushState() 和 history.replaceState()都会给它们的第一个参数做一个结构化克隆!需要注意的是,此方法是同步的,因为对浏览器历史记录进行操作的速度不是很快,假如频繁调用这个方法,将会导致浏览器卡死。
const structuredClone = obj => { const oldState = history.state; history.replaceState(obj, null); const clonedObj = history.state; history.replaceState(oldState, null); return clonedObj; };
Via notification API:当创建一个 notification 实例的时候,构造器为它相关的数据做了结构化克隆。需要注意的是,它会尝试向用户展示浏览器通知,但是除非它收到了用户允许展示通知的请求,否则它什么都不会做。一旦用户点击同意的话,notification 会立刻被关闭。
const structuredClone = obj => { const n = new Notification("", {data: obj, silent: true}); nn.onshow = n.close.bind(n); return n.data; };
Node.js 的 8.0.0 版本提供了一个 序列化 api 可以和结构化克隆相媲美. 不过这个 API 在本文发布的时候,还只是被标记为试验性的:
const v8 = require('v8'); const buf = v8.serialize({a: 'foo', b: new Date()}); const cloned = v8.deserialize(buf); cloned.b.getMonth();
在 8.0.0 版本以下比较稳定的方法,可以考虑用 lodash 的 cloneDeep函数,它的思想多少也基于结构化克隆算法。
Javascript 中最好的对象拷贝的算法,很大程度上取决于其使用环境,以及你需要拷贝的对象类型。虽然 lodash 是最安全的泛型深拷贝函数,但是如果你自己封装的话,也许能够获得效率更高的实现方法,以下就是一个简单的深拷贝,对 Date 日期对象也同样适用:
function deepClone(obj) { var copy; // 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] = deepClone(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] = deepClone(obj[attr]); } return copy; } throw new Error("Unable to copy obj as type isn't supported " + obj.constructor.name); }
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。