头图

前言

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())或第三方库。深拷贝适用于修改副本且不影响原始对象的情况,但可能消耗更多资源和时间。了解这两种拷贝方式的差异和应用场景是编写健壮代码的关键。


葡萄城技术团队
2.7k 声望29.1k 粉丝

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。