Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >在 JavaScript 中对象的深拷贝(及其工作原理)[每日前端夜话0x8F]

在 JavaScript 中对象的深拷贝(及其工作原理)[每日前端夜话0x8F]

作者头像
疯狂的技术宅
发布于 2019-07-10 03:41:55
发布于 2019-07-10 03:41:55
2.3K00
代码可运行
举报
文章被收录于专栏:京程一灯京程一灯
运行总次数:0
代码可运行

每日前端夜话0x8F

每日前端夜话,陪你聊前端。

每天晚上18:00准时推送。

正文共:1300 字

预计阅读时间:6 分钟

作者:Chris Chu

翻译:疯狂的技术宅

来源:alligator

如果你打算用 JavaScript 进行编码,那么就需要了解对象的工作方式。对象是 JavaScript 最重要的元素之一,深入理解了它会使你在编码时得心应手。在克隆对象时,它并不像看起来那么简单。

当你不想改变原始对象时,就需要克隆对象。例如,如果你有一个接受对象并改变它的函数,可能不想改变其原始对象。

那么让我们在 JavaScript 中创建一个对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1let testObject = {
2  a: 1,
3  b: 2,
4  c: 3
5};

在上面的代码片段中,我们初始化一个新对象并将其分配给变量 testObject。现在对于大多数初学者来说,他们会试着通过将 testObject 分配给新变量来创建这个对象的副本,以便在其代码中进行操作。很抱歉用这种方法行不通。

下面是一个代码片段,说明了为什么不起作用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1let testObject = {
 2  a: 1,
 3  b: 2,
 4  c: 3
 5};
 6
 7// 为 testObject 创建一个副本
 8let testObjectCopy = testObject;
 9
10testObject.a = 9;
11console.log(testObjectCopy.a);
12// 这里 a = 9

如上面的代码片段所示,创建新变量 testObjectCopy 实际上并不创建 testObject 的副本。相反它只是引用 testObject。你对所谓的副本做的任何更改也将反映在原始对象中。

循环遍历对象并将每个属性复制到新对象也不起作用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1const copyObject = object => {
 2  // 这是存储原始对象属性的对象
 3  let copiedObj = {};
 4
 5  for (let key in object) {
 6    // 这里将每个属性从原始对象复制到复制对象
 7    copiedObj[key] = object[key];
 8  }
 9
10  return copiedObj;
11};
12
13const testObject = {
14  a: 5,
15  b: 6,
16  c: {
17    d: 4
18  }
19};
20
21copyObject(testObject);

上述方法存在以下几个问题:

  • 1. 将每个属性复制到新对象的循环只会复制对象上的可枚举属性。可枚举属性是将要出现在 for 循环和 Object.keys 中的属性。
  • 2. 复制的对象有一个新的 Object.prototype 方法,这不是复制对象时所需的方法。
  • 3. 如果对象具有作为对象的属性,则复制的对象实际上将会引用原始对象而不是创建副本。这意味着如果更改复制对象中的嵌套对象,原始对象也会更改。
  • 4. 不复制任何属性描述符。如果将 configurablewritable 设置为 false,则复制对象中的属性描述符将会默认为 true

那么应该怎样正确的复制对象?

对于仅存储基本类型(如数字和字符串)的简单对象,上述浅层复制方法将起作用。但是如果对象具有对其他嵌套对象的引用,则不会复制实际对象。你只会复制对其的引用

对于深层复制,最简单的选择是使用可靠的外部库,如Lodash

使用 Lodash 的 Clone 和 Clonedeep

Lodash 提供两种不同的功能,允许你进行浅拷贝和深拷贝,它们是 cloneclonedeep。Lodash 的优点在于你可以单独导入它的每个函数,而无需将整个库放入你的项目中。这可以大大的减少依赖项的大小。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1const clone = require('lodash/clone'); 
2const cloneDeep = require('lodash/clonedeep');
3
4// 你也可以这样做:
5// const clone = require('lodash.clone');
6// const cloneDeep = require('lodash.clonedeep');
7// 取决于你自己的风格 :)

现在就用 cloneclonedeep 函数做一些尝试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1const clone = require('lodash/clone'); 
 2const cloneDeep = require('lodash/clonedeep');
 3
 4const externalObject = {
 5  animal: 'Gator'
 6};
 7
 8const originalObject = {
 9  a: 1,
10  b: 'string',
11  c: false,
12  d: externalObject
13};
14
15const shallowClonedObject = clone(originalObject);
16
17externalObject.animal = 'Crocodile';
18
19console.log(originalObject);
20console.log(shallowClonedObject);
21// originalObject 和 shallowClonedObject 中的`animal`属性
22// 是同时被改变的,因为它是一个浅的副本。
23
24const deepClonedObject = clonedeep(originalObject);
25
26externalObject.animal = 'Lizard';
27
28console.log(originalObject);
29console.log(deepClonedObject);
30// 原始对象中的'animal'属性发生了变化,但对于
31// deepClonedObject,它复制后仍然是'Crocodile'
32// 对象是独立的而不是复制引用。

在上面的代码中,我们创建了一个名为 originalObject 的对象,它存储了 7 个属性,每个属性都有不同的值。属性 d 引用我们的 externalObject,它具有值为 Gatoranimal 的属性。

当从 Lodash 执行 clone 函数时,它会创建一个对象的浅层副本,我们将其分配给 shallowClonedObject。在 externalObject 中为 animal 属性赋值一个新值将改变 originalObjectshallowClonedObject,因为浅拷贝只能将引用复制到 externalObject并它没有为自己创造一个全新的对象。

这就是 clonedeep 函数的用武之地。如果你对 deepClonedObject 执行相同的处理,那么 originalObjectd 属性是唯一要改变的属性。

?试一试,看看它如何帮助你编码!?

原文:https://alligator.io/js/deep-cloning-javascript-objects/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端先锋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Javascript的对象拷贝[每日前端夜话0x53]
翻译:疯狂的技术宅 原文:https://smalldata.tech/blog/2018/11/01/copying-objects-in-javascript
疯狂的技术宅
2019/05/06
5070
Javascript的对象拷贝[每日前端夜话0x53]
完全搞懂 Javascript 中的... [每日前端夜话0xE3]
曾几何时,ES6/ES2015 对 Javascript 语言进行了重大升级。它引入了许多不同的新功能。其中之一就是我们可以用在任何兼容容器(对象、数组、字符串、集合、映射)前面的三个连续点。这些小点使我们能够编写更加优雅和简洁的代码。在本文中我将会解释这三个点的工作原理,并展示最常见的例子。
疯狂的技术宅
2019/11/14
6910
JavaScript 的未来:它还少些什么? [每日前端夜话0x28]
近年来,JavaScript 的功能得到了大幅度的增加,本文探讨了其仍然缺失的东西。
疯狂的技术宅
2019/03/27
5380
JavaScript 的未来:它还少些什么? [每日前端夜话0x28]
一篇文章完全掌握 JavaScript 数组操作[每日前端夜话0x87]
可以用被称为方法的多个操作来操作数组。这些方法允许我们对数组进行添加、删除、修改挤执行更多操作。
疯狂的技术宅
2019/06/20
1.1K0
一篇文章完全掌握 JavaScript 数组操作[每日前端夜话0x87]
共享可变状态中出现的问题以及如何避免[每日前端夜话0xDB]
这里有两个独立的部分:函数logElements()和函数main()。后者想要在对数组进行排序的前后都打印其内容。但是它到用了 logElements() ,会导致数组被清空。所以 main() 会在A行输出一个空数组。
疯狂的技术宅
2019/11/03
1.6K0
怎样在JavaScript中创建和填充任意长度的数组 [每日前端夜话0x29]
不过这并不是长久之计,比如当我们需要创建大型数组时。这篇博文探讨了在这种情况下应该怎么做。
疯狂的技术宅
2019/03/27
3.3K0
怎样在JavaScript中创建和填充任意长度的数组 [每日前端夜话0x29]
在 JavaScript 中优雅的提取循环内的数据 [每日前端夜话0x2D]
从 A 行开始的循环用来记录文件路径。它是 for-of 循环和递归的组合(递归调用在 B 行)。
疯狂的技术宅
2019/03/27
3.7K0
在 JavaScript 中优雅的提取循环内的数据 [每日前端夜话0x2D]
图解对象之:深拷贝与浅拷贝
变量存储的不是对象自身,而是该对象的“内存地址”,换句话说就是一个对该对象的“引用”。
微芒不朽
2022/09/06
3350
图解对象之:深拷贝与浅拷贝
七个简单但棘手的 JS 面试问题[每日前端夜话0xD4]
如果你参加 JavaScript 高级开发面试,那么很有可能在编码面试中被问到一些棘手的问题。
疯狂的技术宅
2019/10/22
7550
《现代Javascript高级教程》JavaScript深拷贝与浅拷贝
在JavaScript中,对象的拷贝是一项常见的操作。浅拷贝和深拷贝是两种常用的拷贝方式。浅拷贝只复制对象的引用,而深拷贝创建了一个全新的对象,包含与原始对象相同的值和结构。深拷贝和浅拷贝各有适用的场景和注意事项。本文将详细介绍如何实现一个完整而优雅的深拷贝函数,处理循环引用和特殊类型,优化性能,并探讨深拷贝和浅拷贝的应用场景、注意事项和相关属性。
linwu
2023/07/27
6290
【译】如何在JavaScript中复制Object
不管在什么编程语言中,复制一个对象的值而不是它的引用都是一个十分常见的工作。复值对象的值和复制对象的引用的区别在与通过复制值可以得到两个有着相同值或数据,但是毫不相干的对象,复制引用意味着得到的两个对象在内存中指向相同的数据块。当objet A和object B都引用自相同的底层数据时,只要你操作object A,就会修改到object B。
腾讯IVWEB团队
2020/06/28
2.2K0
在现代 JavaScript 中编写异步任务[每日前端夜话0xDD]
在本文中,我们将探讨过去异步执行的 JavaScript 的演变,以及它是怎样改变我们编写代码的方式的。我们将从最早的 Web 开发开始,一直到现代异步模式。
疯狂的技术宅
2019/11/03
2.4K0
杀手级的TypeScript功能:const断言[每日前端夜话0x6F]
我发现官方的 TypeScript 文档非常有用,但是总觉得有点过于学术化并且枯燥无味。每当我发现一个新功能时,我想要知道这个功能究竟能够解决什么问题而不是长篇大论。
疯狂的技术宅
2019/05/21
1.2K0
正则表达式在 ES2018 中的新写法 [每日前端夜话0x25]
摘要:如果你曾用 JavaScript 做过复杂的文本处理和操作,那么你将会对 ES2018 中引入的新功能爱不释手。 在本文中,我们将详细介绍第 9 版标准如何提高 JavaScript 的文本处理能力。
疯狂的技术宅
2019/03/27
9650
正则表达式在 ES2018 中的新写法  [每日前端夜话0x25]
在 Node.js 中通过子进程操作标准输入/输出 [每日前端夜话0x2A]
在本中,我们在 Node.js 中把 shell 命令作为子进程运行。然后异步读取这些进程的 stdout 并写入其 stdin。
疯狂的技术宅
2019/03/27
3.3K0
在 Node.js 中通过子进程操作标准输入/输出 [每日前端夜话0x2A]
推荐一个基于 Node.js 的表单验证库 [每日前端夜话0x23]
API 在执行过程中的一个基本任务是数据验证。 在本文中,我想向你展示如何为你的数据添加防弹验证,同时返回风格良好的格式。
疯狂的技术宅
2019/03/27
2.7K0
别在不知道临时死区的情况下使用 JavaScript 变量[每日前端夜话0xD2]
正确答案:第一个代码段(带有类)将生成 ReferenceError。第二个工作正常。
疯狂的技术宅
2019/10/17
7560
太卷了!浏览器也支持原生的深拷贝API了?
在以前,由于浏览器并未对这个能力提供原生支持,所以它经常出现在 手写XXX 这样的面试题中,我之前也为它专门写过一篇文章:
ConardLi
2021/12/24
1.3K0
太卷了!浏览器也支持原生的深拷贝API了?
你可能错过的现代 JavaScript 特性 [每日前端夜话0xE0]
尽管我在过去 7 年中几乎每天都在写 JavaScript 代码,但不得不承认,我实际上并不是很注意 ES 语言的发布声明。async/await 和 Proxies 之类的主要特性是一回事,但是每年都有稳定的小规模、渐进式的改进在不断涌现,因为总有一些东西需要学习。
疯狂的技术宅
2019/11/14
4810
解决 JavaScript 中处理 null 和 undefined 的麻烦事[每日前端夜话0xE6]
许多 JavaScript 开发人员正在为怎么处理可选值头痛。有什么好办法来最大程度地减少由值(可能为 null、undefined或在运行时未初始化)引起的错误?
疯狂的技术宅
2019/11/14
1.3K0
推荐阅读
相关推荐
Javascript的对象拷贝[每日前端夜话0x53]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验