文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

javascript中怎么区分浅拷贝和深拷贝并实现深拷贝

2024-04-02 19:55

关注

这篇文章将为大家详细讲解有关javascript中怎么区分浅拷贝和深拷贝并实现深拷贝,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

什么是拷贝 ?

一个东西的拷贝看起来像是原来的东西,然而它并不是。同时,当你改变拷贝时,原来的东西可不会发生变化。

在编程时,我们把值存储在变量里,拷贝意味着用原变量初始化了一个新变量。请注意,拷贝具有两个不同的概念:深拷贝(deep copying) 与  浅拷贝(shallow copying)。深拷贝意味着新变量的所有值都被复制且与原变量毫不相关;浅拷贝则表示着新变量的某些(子)值仍然与原变量相关。

为了更好的理解深拷贝与浅拷贝,我们需要知道,JavaScript 是如何存储一个值的。

值的存储方式

原始数据类型

原始数据类型包括:

这些类型的值与指定给它们的变量紧密相连,也不会同时与多个变量关联,这意味着你并不需要担心在JavaScript  中复制这些原始数据类型时发生意外:复制它们得到的是一个确确实实独立的副本。

我们来看一个例子:

const a = 5 let b = 6 // 创建 a 的拷贝 console.log(b) // 6 console.log(a) // 5

通过执行 b = a ,就可以得到 a 的拷贝。此时,将新值重新指定给 b 时,b 的值会改变,但 a 的值不会随之发生变化。

javascript中怎么区分浅拷贝和深拷贝并实现深拷贝

复合数据类型—— Object 与数组

技术上看,数组也是 Object 对象,所以它们有着相似的表现。关于这点,后文我们会详细地介绍。

在这里,拷贝变得耐人寻味了起来:复合类型的值在被实例化时仅会被创建一次。也就是说,如果我们进行复合类型的拷贝,实际上是分配给拷贝一个指向原对象的引用。

const a = {     en: 'Hello',     de: 'Hallo',     es: 'Hola',     pt: 'Olà' } let b = a b.pt = 'Oi' console.log(b.pt) // Oi console.log(a.pt) // Oi

上面的实例展示了浅拷贝的特征。通常而言,我们并不期望得到这种结果——原变量 a 并不应该受到新变量 b  的影响。当我们访问原变量时,往往造成出乎意料的错误。因为你不清楚错误的原因,可能会在造成错误后进行一会儿的调试,接着“自暴自弃”了起来。

javascript中怎么区分浅拷贝和深拷贝并实现深拷贝

不用急,让我们看看一些实现深拷贝的方法。

实现深拷贝的方法

Object

有许多方法可以确实地复制一个对象,其中新的 JavaScript 规范提供了我们一种非常快捷的方式。

展开运算符(Spread operator)

它在 ES2015 中被引入,它太吊了,因为它实在是简洁方便。它可以把原变量“展开”到一个新的变量中。使用方式如下:

const a = {     en: 'Bye',     de: 'Tschüss' } let b = {...a} // 没错!就这么简单 b.de = 'Ciao' console.log(b.de) // Ciao console.log(a.de) // Tschüss

也可以使用它把两个对象合并在一起,例如 const c = {... a,... b}。

Object.assign

这种方法在展开运算符出现之前被广泛采用,基本上与后者相同。但在使用它时你可得小心,因为 Object.assign()  方法的第一个参数会被修改然后返回,所以一般我们会传给第一个参数一个空对象,防止被意外修改。然后,传你想复制的对象给第二个参数。

const a = {     en: 'Bye',     de: 'Tschüss' } let b = Object.assign({}, a) b.de = 'Ciao' console.log(b.de) // Ciao console.log(a.de) // Tschüss

陷阱:嵌套的 Object 对象

在复制一个对象时有个很大的陷阱,你也许也发现了,这个陷阱存在于上述的两种拷贝方法:当你有一个嵌套的对象(数组)并试图深拷贝它们时。该对象内部的对象并不会以同样的方式被拷贝下来——它们会被浅拷贝。因此,如果你更改得到的拷贝里的对象,原对象里的对象也将改变。下面是此错误的示例:

const a = {     foods: {       dinner: 'Pasta'     } } let b = {...a} b.foods.dinner = 'Soup' // dinner 并未被深拷贝 console.log(b.foods.dinner) // Soup console.log(a.foods.dinner) // Soup

要得到让对象里的对象得到预期的深拷贝,你必须手动复制所有嵌套对象:

const a = {     foods: {       dinner: 'Pasta'     } } let b = {foods: {...a.foods}} b.foods.dinner = 'Soup' console.log(b.foods.dinner) // Soup console.log(a.foods.dinner) // Pasta

如果要拷贝的对象里不止一个对象( foods),可以再次利用一下展开运算符。也就是这样:const b = {... a,foods:{...  a.foods}}。

简单粗暴的深拷贝方式

如果你不知道对象有多少层嵌套呢?手动遍历对象并手动复制每个嵌套对象可十分繁琐。有一种方法能粗暴地拷贝下对象。只需将对象转换为字符串(stringify),然后解析一下(parse)它就完事啦:

const a = {     foods: {       dinner: 'Pasta'     } } let b = JSON.parse(JSON.stringify(a)) b.foods.dinner = 'Soup' console.log(b.foods.dinner) // Soup console.log(a.foods.dinner) // Pasta

如果使用这种方法,你得明白这是无法完全复制自定义类实例的。所以只有拷贝仅有 本地JavaScript值(native JavaScript values)  的对象时才可以使用此方式。

水平不够,翻译不好,放下原文:

建议先不纠结,后文有细说。

数组

拷贝数组和拷贝对象相仿,因为数组本质上也是一种对象。

展开运算符

操作起来和对象一样:

const a = [1,2,3] let b = [...a] b[1] = 4 console.log(b[1]) // 4 console.log(a[1]) // 2

数组方法:map, filter, reduce

运用这些方法可以得到一个新的数组,里面包含原数组里的所有值(或部分)。在拷贝过程中还可以修改你想修改的值,上帝啊,这也太方便了吧。

const a = [1,2,3] let b = a.map(el => el) b[1] = 4 console.log(b[1]) // 4 console.log(a[1]) // 2

或者在复制时修改所需的元素:

const a = [1,2,3] const b = a.map((el, index) => index === 1 ? 4 : el) console.log(b[1]) // 4 console.log(a[1]) // 2

Array.slice

slice 方法通常用于返回数组的子集。数组的子集从数组的特定下标开始,也可以自定义结束的位置。使用 array.slice() 或  array.slice(0) 时,可以得到 array 数组的拷贝。

const a = [1,2,3] let b = a.slice(0) b[1] = 4 console.log(b[1]) // 4 console.log(a[1]) // 2

多维数组(Nested arrays,嵌套数组)

和 Object  一样,使用上面的方法并不会将内部元素进行同样的深拷贝。为了防止意外,可以使用JSON.parse(JSON.stringify(someArray))  。

奖励(BONUS):复制自定义类的实例

当你已是专业的 JavaScript  开发人员,并也要复制自定义构造函数或类时,前面已有提到:你不能简单地将他们转为字符串然后解析,否则实例的方法会遗失。Don't panic!可以自己定义一个  Copy 方法来得到一个具有所有原对象值的新对象,看看具体实现:

class Counter {     constructor() {         this.count = 5     }     copy() {         const copy = new Counter()         copy.count = this.count         return copy     } } const originalCounter = new Counter() const copiedCounter = originalCounter.copy() console.log(originalCounter.count) // 5 console.log(copiedCounter.count) // 5 copiedCounter.count = 7 console.log(originalCounter.count) // 5 console.log(copiedCounter.count) // 7

如果要将对象内部的对象也运用深拷贝,你得灵活使用有关深拷贝的新技能。我将为自定义构造函数的拷贝方法添加最终的解决方法,使它更加动态。

使用此拷贝方法,你可以在构造函数中防止任意数量地值,而不再需要一一赋值。

关于“javascript中怎么区分浅拷贝和深拷贝并实现深拷贝”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

阅读原文内容投诉

免责声明:

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

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

软考中级精品资料免费领

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

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

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

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

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

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

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