前言
所谓深复制与浅复制(深拷贝与浅拷贝),乍一听感觉听高大上,像是一个非常难理解的概念,其实我们平常项目开发都是在用的,只是你可能不知道该怎么叫它的名字而已,就像你听熟了一首歌,就是不知道这首歌叫什么名字一样。
在javascript中有两种数据类型,一种是基本类型,另一种是引用类型,今天说讨论的深复制与浅复制就是和这两种数据类型有直接的关系。
1、基本类型的复制
js种的基本类型有Number Boolean String undefined null这五类,在声明的时候,基本类型的数据都是存放在栈内存中,如下图:
首先声明的a变量会存储在栈内存里,然后又在栈内存里复制了一个a并且赋值给b,此时栈内存里汇总新分配一个内存给不,a和b的值都是abc;然后给b重新赋值,由于a和b都是独立存在的两个变量,所以两个值任何一个变化,都不会影响另外一个变量的值;通俗点来说,就是你有一个叫做a的word文档,原本在D盘里,你把它复制了一份仍在了桌面上,这个时候你对两个文件的编辑并不会相互影响,如上图。
2、引用类型的复制
引用类型,即object,function的值都是存在堆内存中,栈内存中通过指针指向这个值在堆中的地址,而对于引用类型来说普通的赋值操作只是将指针的地址复制了一份给新的变量,属于浅复制,如下图:
这里将arr赋值给arr1,其实就是复制了arr的指针,所以arr1的指向地址和arr是一样的,这样不管操作arr还是arr1都会改变堆中数组的值,打印arr或者arr1都是改变后的值,这种情况只是改变了指针的指向,还用文件复制的形式来解释的话就是复制了一个文件的快捷方式,把它仍在桌面上,打开的仍然是源文件,如上图。
但是现在有一个需求,就是后台请求过来的json对象,在对其处理之前需要拷贝一份以方便其他开发人员再开发,如果我们用上面的赋值方式复制一份出来肯定是不行的,这样的话你下面对数据的处理也会改变源数据的值,那应该怎么处理呢?看下面代码
1 function copy(obj) { 2 let Tobj = {} 3 for(var key in obj) { 4 Tobj[key] = obj[key] // 遍历复制属性 5 } 6 return Tobj 7 } 8 9 var obj = { 10 name: "MrGao", 11 age: 24 12 }; 13 var obj1 = copy(obj); 14 obj1.name = "MrBone" 15 console.log(obj.name) // "MrGao" 16 console.log(obj1.name) // "MrBone"
这样基于重新复制了一份数据出来,而且下面如果你对复制出来的数据进行修改,也不会影响源数据
但是,如果出现下面这种情况还是会有问题
1 var obj = { 2 name: "MrGao", 3 age: 24, 4 skill: { 5 work: "javascript", 6 life: "singer" 7 } 8 }; 9 function copy(obj) { 10 let Tobj = {} 11 for(var key in obj) { 12 Tobj[key] = obj[key] 13 } 14 return Tobj 15 } 16 var obj1 = copy(obj); 17 obj1.skill.work = "java" 18 console.log(obj.skill.work) // java 19 console.log(obj1.skill.work) // java
如此看来,值还是都改变了,上面两者只是obj中多了一个skill对象而已,由于遍历复制的只是obj中的属性,所以obj.skill作为一个引用类型对象,也只是复制了一个指针过去,所以当obj1中的skill发生了变化,obj中的skill也会变化,那么如何解决这个问题呢?
1 var obj = { 2 name: "MrGao", 3 age: 24, 4 skill: { 5 work: "javascript", 6 life: "singer" 7 } 8 }; 9 function copy(obj) { 10 let Tobj = {} 11 for(var key in obj) { 12 if (typeof(obj[key]) == 'object') { 13 Tobj[key] = copy(obj[key]) 14 } else { 15 Tobj[key] = obj[key] 16 } 17 } 18 return Tobj 19 } 20 var obj1 = copy(obj); 21 obj1.skill.work = "java" 22 console.log(obj.skill.work) // javascript 23 console.log(obj1.skill.work) // java
为了方便理解,这里只考虑到传入的值为obj,如此,当遍历到key的值为'object'类型时,这递归调用本方法,来完成对内层对象的复制。
除了上面这种方法外,还有一种方法可以实现深复制
1 var obj = { 2 name: "MrGao", 3 age: 24, 4 skill: { 5 work: "javascript", 6 life: "singer" 7 } 8 }; 9 var obj1 = JSON.parse(JSON.stringify(obj)); // 先将obj转为字符串再复制 10 obj1.skill.work = "java" 11 console.log(obj.skill.work) // javascript 12 console.log(obj1.skill.work) // java
这种方法为先将obj转化为字符串,复制的时候就会重新在栈内存中复制一份,然后JSON.parse之后重新赋值给obj1,就实现了对对象的深复制。
总结
浅复制:之前理解的是浅复制指的是对引用对象指针的复制,并没有复制其本质内容;后来对浅复制的理解为对引用对象一层复制为浅复制,而对应的深复制为以上两种对对象进行深复制的方法。
深复制:深复制就是对一个对象通过递归,或类型转换的全面复制。
对于以上纯属个人理解,如有纰漏,欢迎指正!