文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Javascript的对象拷贝

2024-12-03 07:14

关注

在开始之前,我先普及一些基础知识。Javascript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以重新被赋值。所以仅仅复制这个指针,其结果是有两个指针指向内存中的同一个地址。

  1. var foo = { 
  2.     a : "abc" 
  3. console.log(foo.a); 
  4. // abc 
  5.  
  6. var bar = foo
  7. console.log(bar.a); 
  8. // abc 
  9.  
  10. foo.a = "yo foo"
  11. console.log(foo.a); 
  12. // yo foo 
  13. console.log(bar.a); 
  14. // yo foo 
  15.  
  16. bar.a = "whatup bar?"
  17. console.log(foo.a); 
  18. // whatup bar? 
  19. console.log(bar.a); 
  20. // whatup bar?     

通过上面的例子可以看到,对象 foo 和 bar 都能随着对方的变化而变化。所以在拷贝 Javascript 中的对象时,要根据实际情况做一些考虑。

浅拷贝

如果要操作的对象拥有的属性都是值类型,那么可以使用扩展语法或 Object.assign(...)

  1. var obj = { foo: "foo", bar: "bar" }; 
  2. var copy = { ...obj }; 
  3. // Object { foo: "foo", bar: "bar" } 
  4. var obj = { foo: "foo", bar: "bar" }; 
  5. var copy = Object.assign({}, obj); 
  6. // Object { foo: "foo", bar: "bar" } 

可以看到上面两种方法都可以把多个不同来源对象中的属性复制到一个目标对象中。

  1. var obj1 = { foo: "foo" }; 
  2. var obj2 = { bar: "bar" }; 
  3. var copySpread = { ...obj1, ...obj2 }; 
  4. // Object { foo: "foo", bar: "bar" } 
  5. var copyAssign = Object.assign({}, obj1, obj2); 
  6. // Object { foo: "foo", bar: "bar" } 

上面这种方法是存在问题的,如果对象的属性也是对象,那么实际被拷贝的只是那些指针,这跟执行 var bar = foo; 的效果是一样的,和第一段代码中的做法一样。

  1. var foo = { a: 0 , b: { c: 0 } }; 
  2. var copy = { ...foo }; 
  3. copy.a = 1
  4. copy.b.c = 2
  5. console.dir(foo); 
  6. // { a: 0, b: { c: 2 } } 
  7. console.dir(copy); 
  8. // { a: 1, b: { c: 2 } } 

深拷贝(有限制)

想要对一个对象进行深拷贝,一个可行的方法是先把对象序列化为字符串,然后再对它进行反序列化。

  1. var obj = { a: 0, b: { c: 0 } }; 
  2. 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: 其原理是借用了通信中用到的序列化算法。由于它是基于事件的,所以这里的克隆也是一个异步操作。

  1. class StructuredCloner { 
  2.   constructor() { 
  3.     this.pendingClones_ = new Map(); 
  4.     this.nextKey_ = 0
  5.  
  6.     const channel = new MessageChannel(); 
  7.     this.inPort_ = channel.port1; 
  8.     this.outPort_ = channel.port2; 
  9.  
  10.     this.outPort_.onmessage = ({data: {key, value}}) => { 
  11.       const resolve = this.pendingClones_.get(key); 
  12.       resolve(value); 
  13.       this.pendingClones_.delete(key); 
  14.     }; 
  15.     this.outPort_.start(); 
  16.   } 
  17.  
  18.   cloneAsync(value) { 
  19.     return new Promise(resolve => { 
  20.       const key = this.nextKey_++; 
  21.       this.pendingClones_.set(key, resolve); 
  22.       this.inPort_.postMessage({key, value}); 
  23.     }); 
  24.   } 
  25.  
  26. const structuredCloneAsync = window.structuredCloneAsync = 
  27.     StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner); 
  28.  
  29. const main = async () => { 
  30.   const original = { date: new Date(), number: Math.random() }; 
  31.   originaloriginal.self = original; 
  32.  
  33.   const clone = await structuredCloneAsync(original); 
  34.  
  35.   // different objects: 
  36.   console.assert(original !== clone); 
  37.   console.assert(original.date !== clone.date); 
  38.  
  39.   // cyclical: 
  40.   console.assert(original.self === original); 
  41.   console.assert(clone.self === clone); 
  42.  
  43.   // equivalent values: 
  44.   console.assert(original.number === clone.number); 
  45.   console.assert(Number(original.date) === Number(clone.date)); 
  46.  
  47.   console.log("Assertions complete."); 
  48. }; 
  49.  
  50. main(); 

Via the history API:history.pushState() 和 history.replaceState()都会给它们的第一个参数做一个结构化克隆!需要注意的是,此方法是同步的,因为对浏览器历史记录进行操作的速度不是很快,假如频繁调用这个方法,将会导致浏览器卡死。

  1. const structuredClone = obj => { 
  2.   const oldState = history.state; 
  3.   history.replaceState(obj, null); 
  4.   const clonedObj = history.state; 
  5.   history.replaceState(oldState, null); 
  6.   return clonedObj; 
  7. }; 

Via notification API:当创建一个 notification 实例的时候,构造器为它相关的数据做了结构化克隆。需要注意的是,它会尝试向用户展示浏览器通知,但是除非它收到了用户允许展示通知的请求,否则它什么都不会做。一旦用户点击同意的话,notification 会立刻被关闭。

  1. const structuredClone = obj => { 
  2.   const n = new Notification("", {data: obj, silent: true}); 
  3.   nn.onshow = n.close.bind(n); 
  4.   return n.data; 
  5. }; 

用 Node.js 进行深拷贝

Node.js 的 8.0.0 版本提供了一个 序列化 api 可以和结构化克隆相媲美. 不过这个 API 在本文发布的时候,还只是被标记为试验性的:

  1. const v8 = require('v8'); 
  2. const buf = v8.serialize({a: 'foo', b: new Date()}); 
  3. const cloned = v8.deserialize(buf); 
  4. cloned.b.getMonth(); 

在 8.0.0 版本以下比较稳定的方法,可以考虑用 lodash 的 cloneDeep函数,它的思想多少也基于结构化克隆算法。

结论

Javascript 中最好的对象拷贝的算法,很大程度上取决于其使用环境,以及你需要拷贝的对象类型。虽然 lodash 是最安全的泛型深拷贝函数,但是如果你自己封装的话,也许能够获得效率更高的实现方法,以下就是一个简单的深拷贝,对 Date 日期对象也同样适用:

  1. function deepClone(obj) { 
  2.   var copy; 
  3.  
  4.   // Handle the 3 simple types, and null or undefined 
  5.   if (null == obj || "object" != typeof obj) return obj; 
  6.  
  7.   // Handle Date 
  8.   if (obj instanceof Date) { 
  9.     copy = new Date(); 
  10.     copy.setTime(obj.getTime()); 
  11.     return copy; 
  12.   } 
  13.  
  14.   // Handle Array 
  15.   if (obj instanceof Array) { 
  16.     copy = []; 
  17.     for (var i = 0len = obj.length; i < len; i++) { 
  18.         copy[i] = deepClone(obj[i]); 
  19.     } 
  20.     return copy; 
  21.   } 
  22.  
  23.   // Handle Function 
  24.   if (obj instanceof Function) { 
  25.     copy = function() { 
  26.       return obj.apply(this, arguments); 
  27.     } 
  28.     return copy; 
  29.   } 
  30.  
  31.   // Handle Object 
  32.   if (obj instanceof Object) { 
  33.       copy = {}; 
  34.       for (var attr in obj) { 
  35.           if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]); 
  36.       } 
  37.       return copy; 
  38.   } 
  39.  
  40.   throw new Error("Unable to copy obj as type isn't supported " + obj.constructor.name); 

我很期待可以随便使用结构化克隆的那一天的到来,让对象拷贝不再令人头疼^_^

 

来源:前端先锋内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯