QQ 老汤微信二维码

js深拷贝的5种实现方式(深拷贝代码实现详解)

 分类: js知识时间:2023-04-15 07:30:01点击:


0.什么是深拷贝

深拷贝的就是在拷贝的时候,需要将当前要拷贝的对象内的所有引用类型的属性进行完整的拷贝,也就是说拷贝出来的对象和原对象之间没有任何数据是共享的,所有的东西都是自己独占的一份。

如何在 JavaScript 中拷贝一个对象?对于这个很简单的问题,但是答案却不简单。

1.引用传值

在 JavaScript 中所有的东西都是引用传递。如果你不知道什么意思,看看下面的例子:

function mutate(obj) {
  obj.a = true;
}
const obj = {a: false};
mutate(obj)
console.log(obj.a); // 输出 true

函数 mutate 改变了它的参数。在值传递的场景中,函数的形参只是实参的一个副本——a copy——当函数调用完成后,并不改变实参。但是在 JavaScript 这种引用传递的场景中,函数的形参和实参指向同一个对象,当参数内部改变形参的时候,函数外面的实参也被改变了。

因此在某些情况下,你需要保留原始对象,这时你需要把原始对象的一个拷贝传入到函数中,以防止函数改变原始对象。

2.浅拷贝:Object.assign()

一个简单的获取对象拷贝的方式是使用 Object.assign(target, sources...)。它接受任意数量的源对象,枚举它们的所有属性并分配给target。如果我们使用一个新的空对象target,那么我们就可以实现对象的复制。

const obj = /* ... */;
const copy = Object.assign({}, obj);

然而这只是一个浅副本。如果我们的对象包含其它对象作为自己的属性,它们将保持共享引用,这不是我们想要的:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}
const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用
Object.getOwnPropertyDescriptor()和Object.defineProperty() 。

所以现在怎么办?有几种方法可以创建一个对象的深拷贝。

注意:也许有人提到了对象解构运算,这也是浅拷贝。

3.深拷贝的几种方式

3.1 JSON.parse

创建对象副本的最古老方法之一是:将该对象转换为其 JSON 字符串表示形式,然后将其解析回对象。这感觉有点压抑,但它确实有效:

const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));

这里的缺点是你创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。另一个缺点是这种方法不能处理循环对象。而且循环对象经常发生。例如,当您构建树状数据结构,其中一个节点引用其父级,而父级又引用其子级。

const x = {};
const y = {x};
x.y = y; 
// Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); 
// throws!

另外,诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

3.2 Structured Clone 结构化克隆算法

Structured cloning 是一种现有的算法,用于将值从一个地方转移到另一地方。例如,每当您调用postMessage将消息发送到另一个窗口或 WebWorker 时,都会使用它。关于结构化克隆的好处在于它处理循环对象并支持大量的内置类型。问题是,在编写本文时,该算法并不能直接使用,只能作为其他 API 的一部分。

目前浏览器已经实现了structuredClone方法。

3.3 MessageChannel

正如我所说的,只要你调用postMessage结构化克隆算法就可以使用。我们可以创建一个 MessageChannel 并发送消息。在接收端,消息包含我们原始数据对象的结构化克隆。

function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}
const obj = /* ... */;
const clone = await structuralClone(obj);

这种方法的缺点是它是异步的。虽然这并无大碍,但是有时候你需要使用同步的方式来深度拷贝一个对象。

3.4 History API

如果你曾经使用history.pushState()写过 SPA,你就知道你可以提供一个状态对象来保存 URL。事实证明,这个状态对象使用结构化克隆 - 而且是同步的。我们必须小心使用,不要把程序逻辑使用的状态对象搞乱了,所以我们需要在完成克隆之后恢复原始状态。为了防止发生任何意外,请使用history.replaceState()而不是history.pushState()。

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);
  return copy;
}
const obj = /* ... */;
const clone = structuralClone(obj);

然而,仅仅为了复制一个对象,而使用浏览器的引擎,感觉有点过分。另外,Safari 浏览器对replaceState调用的限制数量为 30 秒内 100 次。

3.5 Notification API

在发了一条推文之后,Jeremy Banks 向我展示了第三种方法来利用结构化克隆:Notification API。

function structuralClone(obj) {
  return new Notification('', {data: obj, silent: true}).data;
}
const obj = /* ... */;
const clone = structuralClone(obj);

短小,简洁。我喜欢它!

但是,它需要浏览器内部的权限机制,所以我怀疑它是很慢的。由于某种原因,Safari 总是返回undefined。

4.Performance extravaganza

我想测量哪种方法是最高性能的。在我的第一次(天真的)尝试中,我拿了一个小 JSON 对象,并通过不同的方式克隆对象 1 千次。幸运的是,Mathias Bynens 告诉我,当你添加属性到一个对象时,V8有一个缓存。所以我是在给缓存做基准测试。为了确保我永远不会碰到缓存,我编写了一个函数,使用随机密钥名称生成给定深度和宽度的对象,并重新运行测试。

以下是 Chrome,Firefox 和 Edge 中不同技术的性能。越低越好。



5.结论

那么我们从中得到了什么呢?

  • 如果您没有循环对象,并且不需要保留内置类型,则可以使用跨浏览器的JSON.parse(JSON.stringify())获得最快的克隆性能,这让我感到非常惊讶。

  • 如果你想要一个适当的结构化克隆,MessageChannel是你唯一可靠的跨浏览器的选择。

目前浏览器平台直接提供一个 structuredClone()函数。

除注明外的文章,均为来源:老汤博客,转载请保留本文地址!
原文地址: https://tangjiusheng.com/js/3990.html
  • 关注下 头条号后,请私信将免费分享给你一份web前端资料!
  • 立即关注
大家都在看
  • js深拷贝的5种实现方式(深拷贝代码实现详解)
  • js深拷贝的三种实现方式(附JQ的extend深拷贝方法)
  • js深拷贝和浅拷贝的区别(深拷贝和浅拷贝的实现方式)

深圳SEO优化公司上饶百度竞价公司曲靖网站关键词优化推荐观澜seo优化哪家好同乐网站建设设计推荐南昌网站关键词优化推荐邯郸网站推广系统丹竹头英文网站建设多少钱恩施百度网站优化排名公司鹤壁seo排名价格延边如何制作网站公司泰安模板网站建设价格四平设计公司网站价格北京网站设计价格厦门企业网站建设昌都设计公司网站推荐盘锦企业网站改版丽江营销网站哪家好观澜建设网站价格福州SEO按天收费报价宣城seo优化价格台州关键词按天收费价格丹竹头seo网站优化哪家好绍兴企业网站制作景德镇网站推广推荐临猗网站推广价格莱芜设计网站德阳seo石岩网站优化推荐舟山百度标王推荐盘锦阿里店铺运营多少钱歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化