javascript中怎么区分浅拷贝和深拷贝并实现深拷贝
这篇文章将为大家详细讲解有关javascript中怎么区分浅拷贝和深拷贝并实现深拷贝,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
成都创新互联专注于绥江企业网站建设,成都响应式网站建设,商城网站制作。绥江网站建设公司,为绥江等地区提供建站服务。全流程按需求定制网站,专业设计,全程项目跟踪,成都创新互联专业和态度为您提供的服务
什么是拷贝 ?
一个东西的拷贝看起来像是原来的东西,然而它并不是。同时,当你改变拷贝时,原来的东西可不会发生变化。
在编程时,我们把值存储在变量里,拷贝意味着用原变量初始化了一个新变量。请注意,拷贝具有两个不同的概念:深拷贝(deep copying) 与 浅拷贝(shallow copying)。深拷贝意味着新变量的所有值都被复制且与原变量毫不相关;浅拷贝则表示着新变量的某些(子)值仍然与原变量相关。
为了更好的理解深拷贝与浅拷贝,我们需要知道,JavaScript 是如何存储一个值的。
值的存储方式
原始数据类型
原始数据类型包括:
Number 如: 1
String 如: 'Hello'
Boolean 如:true
undefined
null
这些类型的值与指定给它们的变量紧密相连,也不会同时与多个变量关联,这意味着你并不需要担心在JavaScript 中复制这些原始数据类型时发生意外:复制它们得到的是一个确确实实独立的副本。
我们来看一个例子:
const a = 5 let b = 6 // 创建 a 的拷贝 console.log(b) // 6 console.log(a) // 5
通过执行 b = a ,就可以得到 a 的拷贝。此时,将新值重新指定给 b 时,b 的值会改变,但 a 的值不会随之发生变化。
复合数据类型—— 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 的影响。当我们访问原变量时,往往造成出乎意料的错误。因为你不清楚错误的原因,可能会在造成错误后进行一会儿的调试,接着“自暴自弃”了起来。
不用急,让我们看看一些实现深拷贝的方法。
实现深拷贝的方法
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) 的对象时才可以使用此方式。
水平不够,翻译不好,放下原文:
Here, you have to consider that you will not be able to copy custom class instances, so you can only use it when you copy objects with native JavaScript values inside.
建议先不纠结,后文有细说。
数组
拷贝数组和拷贝对象相仿,因为数组本质上也是一种对象。
展开运算符
操作起来和对象一样:
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中怎么区分浅拷贝和深拷贝并实现深拷贝”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
文章名称:javascript中怎么区分浅拷贝和深拷贝并实现深拷贝
网站网址:http://pwwzsj.com/article/psgcci.html