JavaScript中的浅拷贝与深拷贝

javascript,拷贝 · 浏览次数 : 231

小编点评

**JavaScript 中的浅拷贝和深拷贝** **浅拷贝** * 创建一个新对象或数组,并将原始对象或数组的引用复制给新对象或数组。 * 新对象和原始对象将共享相同的内存地址。 * 修改其中一个对象的属性或元素,也会影响另一个对象。 **深拷贝** * 创建一个完全独立的对象或数组,新的拷贝将具有与原始对象或数组相同的值,但它们在内存中是彼此独立的。 * 使用 `Object.assign()` 方法可以实现深拷贝。 * 使用 JSON.parse(JSON.stringify()) 方法可以实现深拷贝,但它存在一些局限性。 **示例** **浅拷贝** ```javascript let x = 400; let y = xx = "This string"; console.log(y); // 400 console.log(x); // 400 ``` **深拷贝** ```javascript let myRadio = { podcasts: 19, albums: 378, playlists: 4 }; let deepCopyMyRadio = Object.assign({}, myRadio); deepCopyMyRadio.playlists = 62; console.log(deepCopyMyRadio); // => { podcasts: 9, albums: 38, playlists: 4 } ``` **深拷贝的局限性** * 无法复制函数、正则表达式等非数据类型。 * 在某些情况下可能会带来性能问题。 **选择深拷贝方法** * 修改副本且不影响原始对象。 * 创建一个完全独立的对象或数组。

正文

前言

JavaScript中的浅拷贝和深拷贝是非常重要的概念,它们在处理对象和数组时具有不同的作用。在编程中,经常需要复制数据以便进行各种操作,但必须注意拷贝的方式,以确保得到预期的结果。

浅拷贝是创建一个新对象或数组,并将原始对象或数组的引用复制给它。这意味着新对象和原始对象将共享相同的内存地址,修改其中一个对象的属性或元素也会影响另一个对象。相反,深拷贝是创建一个完全独立的对象或数组,新的拷贝将具有与原始对象或数组相同的值,但是它们在内存中是彼此独立的,相互之间的修改不会互相影响。

本文小编将为大家介绍JavaScript中实现浅拷贝和深拷贝的不同方法,并提供示例代码作为辅助。

基本拷贝

下面是一个基本的拷贝,新的拷贝对象会专门开辟一块内存空间——二者的类型、值都是独立可变的,换句话说,他们是通过将值传递给新对象完成拷贝的。

//原始值拷贝
let x = 400
let y = x
x = "This string"
console.log(y)  //400
console.log(x)  //This string

当y被创建时,它的值被赋予了x的值(因为这是在运行时,x被重新赋值之前)。这里重要的一点是,读者可以通过创建另一个变量并将其分配给要复制的变量来快速将原始数据类型的精确值复制到单独的内存空间中。请注意它是如何实例化的——const 不允许再进行更改。

​ (内存分配和原始赋值的视觉进展)

//小编可以走的更深一些,在上面的代码中,再将x设置为原始数据类型;
//当然了,小编都知道它们是在不同的内存空间,只不过值是相同的
let x = 400
let y = x
x = "This string"
console.log(y)  //400
console.log(x)  //This string
x = 400
console.log(x)  // 400
console.log(y)  // 400

浅拷贝

以下是一个展示浅拷贝的示例。在此示例中,拷贝了一个包含文字的浅对象。由于浅拷贝只会复制原始对象的引用而非值本身,所以被拷贝的对象和原始对象将共享相同的内存空间,即它们的值也将相同。需要注意的是,在 JavaScript 中,“浅对象”是指一种非嵌套且非原始的 JavaScript 数据类型。

// 浅对象的浅拷贝
let ShallowObj= { 
  key1: 1, 
  key2: 2, 
} 
let newObj = ShallowObj// 一个简单的重新分配为 newObj 创建共享内存
ShallowObj.key1 = 5
console.log(shallowObj) // {key1: 5, key2: 2} 
console.log(newObj) // {key1: 5, key2: 2}

当重新分配ShallowObj中key1的值时,会导致newObj中key1的值也随之发生改变。尽管这两个对象具有不同的变量名称,但它们实际上共享相同的内存空间。因此,如果需要更改shallowObj.key1的值,可以直接修改newObj.key1来获得相同的结果。这在某些情况下非常有用,例如当需要表示一组具有相同属性和值的特定对象时。然而,在运行时,可能需要给这些浅拷贝对象赋予不同的变量名称,以满足应用程序的需求,并作为不同的props传递给其他组件。通过使用不同的变量名称,可以根据不同的目标在应用程序中对它们进行独立操作,以实现所需的功能。

对浅对象进行深拷贝

//使用 Object.assign()
let myRadio = { podcasts: 19, 
                albums: 378, 
                playlists: 44 
              }
let deepCopyMyRadio = Object.assign( {}, myRadio )
deepCopyMyRadio.playlists = 62 // 只改变 deepCopyMyRadio
console.log(deepCopyMyRadio) // => { podcasts: 19, 
                                     albums: 378, 
                                    playlists: 62 
                                   }
console.log(myRadio)         // => { podcasts: 19, 
                                     albums: 378, 
                                     playlists: 44 
                                   }

ES6引入了一种新特性称为扩展运算符。扩展运算符用三个连续的点"..."表示,并可以在代码的多个地方使用。通常情况下,扩展运算符会为给定对象的每个顶级属性创建副本,并将它们扩展到新对象中。在特定情况下,可以选择使用浅拷贝或深拷贝来处理嵌套对象。在本例中,展示的是浅对象的深拷贝,因此可以使用Object.assign()方法或以下示例即可。

//用扩展运算符深拷贝
let myRadio = { podcasts: 9, 
                albums: 38, 
                playlists: 4 
               }
let copyMyRadio = { ...myRadio }
myRadio.albums = 88 // 还是只改变myRadio
console.log(myRadio) //=> { podcasts: 9, albums: 88, playlists: 4 }
console.log(copyMyRadio) //=> {podcasts: 9,albums: 38, playlists: 4}

不过这种写法的问题在于,随着项目规模和复杂性的增加,扩展运算符是一个有局限的解决方案。扩展运算符可以处理浅对象的深拷贝(非嵌套),即将一个对象的顶级属性复制到另一个对象中。然而,当涉及嵌套对象或多层级结构时,扩展运算符会遇到限制。它只能复制对象的第一层属性,而无法递归地复制嵌套的对象。

​ (分配方式:someOtherVar = someVar)

下面我们来看一下展开运算符在处理嵌套对象的复杂性时,并不如预期。对于嵌套对象来说,扩展运算符只提供了第一层属性的深拷贝,而对于所有嵌套的数据来说,它们与原始数据共享内存空间,实际上进行的是浅拷贝。

Population.total 在 city 和shallowCity 中共享一个内存点。扩展运算符获取顶层数据并将其添加到单独的内存空间;因此,shallowCity 的 name 属性实际上已更改。

对深对象进行深拷[JSON.parse(JSON.stringify())]

为了解决嵌套对象的复杂性问题,下面向大家介绍如何在深对象中进行深拷贝。在 JavaScript 中,当需要复制嵌套对象或数组时,深拷贝变得非常重要。深拷贝是一种创建独立全新对象的方法,它递归地复制每个嵌套对象和数组,有效地避免了使用共享内存带来的修改问题。其中,最常用的深拷贝方法是使用JSON.parse(JSON.stringify(object))。该方法首先将原始对象序列化为 JSON 字符串,然后再解析字符串并创建一个新对象,以确保所有属性和嵌套对象都被复制到全新的对象中。当然,需要注意的是该方法存在一定的局限性,例如无法复制函数、正则表达式等非数据类型,并且在某些情况下可能会带来性能问题。因此,在实际应用中,我们必须根据具体情况选择适合的深拷贝方法,以取得效率和正确性的平衡。

​ (对深对象进行深拷贝)

总结

JavaScript中的浅拷贝复制对象是创建一个新对象,但嵌套对象仍然共享内存。而深拷贝则创建一个独立的全新对象,包括嵌套对象在内都被完全复制。浅拷贝常用方法有Object.assign()和扩展运算符,而深拷贝可以使用JSON.parse(JSON.stringify())或第三方库。深拷贝适用于修改副本且不影响原始对象的情况,但可能消耗更多资源和时间。了解这两种拷贝方式的差异和应用场景是编写健壮代码的关键。

与JavaScript中的浅拷贝与深拷贝相似的内容:

JavaScript中的浅拷贝与深拷贝

前言 JavaScript中的浅拷贝和深拷贝是非常重要的概念,它们在处理对象和数组时具有不同的作用。在编程中,经常需要复制数据以便进行各种操作,但必须注意拷贝的方式,以确保得到预期的结果。 浅拷贝是创建一个新对象或数组,并将原始对象或数组的引用复制给它。这意味着新对象和原始对象将共享相同的内存地址,

【JavaScript】聊一聊js中的浅拷贝与深拷贝与手写实现

什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式。‌在聊深浅拷贝之前咱得了解一下js中的两种数据类型:

高级前端开发需要知道的 25 个 JavaScript 单行代码

1. 不使用临时变量来交换变量的值 2. 对象解构,让数据访问更便捷 3. 浅克隆对象 4. 合并对象 5. 清理数组 6. 将 NodeList 转换为数组 7. 检查数组是否满足指定条件 8. 将文本复制到剪贴板 9. 删除数组重复项 10. 取两个数组的交集 11. 求数组元素的总和 12. ...

JavaScript 中的 Range 和 Selection 对象

最近在做鼠标框选的需求,鼠标框选就需要用到 Range 和 Selection 对象。 Range 表示选择的区间范围,Selection 表示选择的文档内容。 Range 接口表示一个包含节点与文本节点的一部分的文档片段。 不仅仅可以用于鼠标框选,页面上任何元素、文本都可以创建 Range。 Se...

JavaScript 中 toString 的奇妙使用

JavaScript 中的toString()方法,我们通常会一些其他类型的变量,转为字符串类型。但这里还有一些其他奇妙的用法。 不同的类型调用 toString() 会得到不同的结果。我们来一一分析下。 1. 函数类型 我们开发者自定义的函数,和 JavaScript 官方内置的函数,在调用 to

JavaScript中的箭头函数

前言 本文可以让你了解所有有关JavaScript箭头函数的信息。我们将告诉你如何使用ES6的箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误。你会看到很多例子来说明它们是如何工作的。 JavaScript的箭头函数随着ECMAScript 2015的发布而到来,也被称为ES6。由于其简洁

JavaScript中的四种枚举方式

字符串和数字具有无数个值,而其他类型如布尔值则是有限的集合。 一周的日子(星期一,星期二,...,星期日),一年的季节(冬季,春季,夏季,秋季)和基本方向(北,东,南,西)都是具有有限值集合的例子。 当一个变量有一个来自有限的预定义常量的值时,使用枚举是很方便的。枚举使你不必使用魔法数字和字符串(这

async/await初学者指南

> JavaScript中的`async`和`await`关键字提供了一种现代语法,帮助我们处理异步操作。在本教程中,我们将深入研究如何使用`async/await`来掌控JavaScript程序中的流程控制。 > ## 总览 - 如何创建JavaScript异步函数 - async关键字 - aw

如何在JavaScript中使用for循环

前言 循环允许我们通过循环数组或对象中的项并做一些事情,比如说打印它们,修改它们,或执行其他类型的任务或动作。JavaScript有各种各样的循环,for循环允许我们对一个集合(如数组)进行迭代。 在这篇文章中,我们将了解JavaScript提供的for循环。我们将看看for...in循环语句是如何

重学JavaScript Promise API

> 在这篇教程中,我们将掌握如何在JavaScript中创建并使用Promise。我们将了解Promise链式调用、错误处理以及最近添加到语言中的一些Promise静态方法。 ## 什么是Promise? 在JavaScript中,一些操作是异步的。这意味着当这些操作完成时,它们产出的结果或者值并不