博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript 深拷贝(deep copy)和浅拷贝(shallow copy)
阅读量:5303 次
发布时间:2019-06-14

本文共 3885 字,大约阅读时间需要 12 分钟。

参考:

  1.  

 

在编程语言中,数据或者值是存放在变量中的。拷贝的意思就是使用相同的值创建新的变量。

当我们改变拷贝的东西时,我们不希望原来的东西也发生改变。

 

深拷贝的意思是这个新变量里的值都是从原来的变量中复制而来,并且和原来的变量没有关联。

浅拷贝的意思是,新变量中存在一些仍然与原来的变量有关联的值。

 

JavaScript 数据类型

原始数据类型 (有的资料叫做基本数据类型):数字、字符串、布尔值、undefined、null

这些值被赋值后就和对应的变量绑定在一起。如果你拷贝这些变量,就是实实在在的拷贝。

b = a 就是一次拷贝,重新给 b 赋值,a 的值不会改变:

const a = 5let b = a // this is the copyb = 6console.log(b) // 6console.log(a) // 5

 

复合数据类型(有的资料叫做引用数据类型)——对象 和 数组

技术上讲,数组也是对象。

这种类型的值,只在初始化的时候存储一次。赋值给变量也仅仅是创建了一个指向这个值的引用。

拷贝 b = a,改变 b 中的属性 pt 的值,a 中包含的 pt 的值也改变了,因为 a 和 b 实际上指向的是同一个对象:

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

 

上面这个例子就是一个浅拷贝

新的对象有着原对象属性的一份精确拷贝。如果属性值是原始类型,拷贝的就是原始类型值,如果属性是引用类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址或者改变了内存中的值,另一个对象的属性也会变化。

也就是说浅拷贝只拷贝了第一层的原始类型值,和第一层的引用类型地址。

 

浅拷贝的场景

 

展开操作符 Spread operator

使用这个操作符可以将所有的属性值复制到新对象中。

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

还可以合并两个对象,比如 const c = { ...a, ...b}.

 

Object.assign()

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,然后返回目标对象。

第一个参数是被修改和最终返回的值,第二个参数是你要拷贝的对象。通常,只需要给第一个参数传入一个空对象,这样可以避免修改已有的数据。

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

 

拷贝数组

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

 

数组方法——map, filter, reduce

这些方法都可以返回新的数组:

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

在拷贝的过程中修改特定的值:

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

 

Array.slice

使用array.slice() 或者 array.slice(0) 你可以得到原数组的拷贝。

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

 

嵌套对象或数组

就算使用了上面的方法,如果对象内部包含对象,那么内部嵌套的对象也不会被拷贝,因为它们只是引用。因此改变嵌套对象,所有的实例中的嵌套对象的属性都会被改变。所以说上面的场景全部都只实现了浅拷贝

const a = {  foods: {    dinner: 'Pasta'  }}let b = {...a}b.foods.dinner = 'Soup' // changes for both objectsconsole.log(b.foods.dinner) // Soupconsole.log(a.foods.dinner) // Soup

 

深拷贝

拷贝所有属性,并拷贝属性指向的动态分配的内存。深拷贝时对象和所引用的对象一起拷贝,相比浅拷贝速度较慢且花销大。拷贝对象和原对象互不影响。

对嵌套的对象进行深拷贝,一种方法是手动拷贝所有嵌套的对象。

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

 

如果对象除了 foods 之外还有很多属性,仍然可以利用展开操作符,比如 

const b = {...a, foods: {...a.foods}}.

 

如果你不知道这个嵌套结构的深度,那么手动遍历这个对象然后拷贝每个嵌套的对象就很麻烦了。

一个很简单的方法就是使用 JSON.stringify 和 JSON.parse

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

 

但是这里要注意的是,你只能使用这种方法拷贝 JavaScript 原生的数据类型(非自定义数据类型)。

而且存在问题

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
// 木易杨let obj = {    name: 'muyiy',    a: undefined,    b: Symbol('muyiy'),    c: function() {}}console.log(obj);// {
// name: "muyiy", // a: undefined, // b: Symbol(muyiy), // c: ƒ ()// }let b = JSON.parse(JSON.stringify(obj));console.log(b);// {name: "muyiy"}

 

// 木易杨let obj = {    a: 1,    b: {        c: 2,           d: 3    }}obj.a = obj.b;obj.b.c = obj.a;let b = JSON.parse(JSON.stringify(obj));// Uncaught TypeError: Converting circular structure to JSON

 

拷贝自定义类型的实例

你不能使用 JSON.stringify 和 JSON.parse 来拷贝自定义类型的数据,下面的例子使用一个自定义的 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) // 5console.log(copiedCounter.count) // 5copiedCounter.count = 7console.log(originalCounter.count) // 5console.log(copiedCounter.count) // 7

 如果实例中有其它对象的引用,就要在copy方法中使用  JSON.stringify 和 JSON.parse 。

 

 除此之外,深拷贝方法还有 jQuery.extend() 和 lodash.cloneDeep()

 总结:

转载于:https://www.cnblogs.com/xiyouchen/p/10366236.html

你可能感兴趣的文章
vue+element-ui路由配置相关
查看>>
一个蓝牙嗅探器的源码
查看>>
[Sdoi2009]Elaxia的路线
查看>>
BZOJ 3170: [Tjoi 2013]松鼠聚会( sort )
查看>>
[1]JAVA概述及开发环境搭建
查看>>
XML 语法规则
查看>>
C++拷贝构造函数
查看>>
BZOJ1856: [Scoi2010]字符串
查看>>
委托action 与func
查看>>
C语言基础
查看>>
java访问redis与redis集群
查看>>
关于UI性能优化
查看>>
自动化测试关键字驱动的原理及实现
查看>>
万能搜索
查看>>
断点续传和下载原理分析
查看>>
ubuntu下下载并安装H265(hm.x.x代码和X265代码)
查看>>
如何学习编程
查看>>
巧用浏览器F12调试器定位系统前后端bug
查看>>
python json.dumps() json.dump()的区别
查看>>
移动端轮播组件swipeslide实现
查看>>