深拷贝和浅拷贝的区别和与原理
一、基本类型和引用类型
基本类型:string,number,boolean,null,undefiend,symbol
引用类型:Function,Array,Object
二、浅拷贝和深拷贝
如图所示:
obj2是对obj1的浅拷贝,obj2新建了一个对象,但是obj2对象复制的是obj1的指针,也就是obj1的堆内存地址,而不是复制对象本身。obj1和obj2是共用了内存地址的。
obj3是对obj1的深拷贝,obj3和obj1不共享内存
因此:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块,
深拷贝是新建一个一模一样的对象,该对象与原对象共享内存,修改新对象也不会影响原对象
三、赋值与浅拷贝
1.赋值
当我们把一个对象赋值给一个变量的时候,赋值的其实是该对象的栈内存地址而不是堆内存数据,(此处看基本类型和引用类型,对象属于引用类型,值分为栈内存的地址和堆内存中的数据)。也就是赋值前的对象和赋值后的对象两个对象共用一个存储空间(赋值的是栈内存地址,而该地址指向了同一个堆内存空间),所以,无论哪个对象发生改变,改变的都是同一个堆堆内存空间。因此,无论修改哪个对象对另一个对象都是有影响的
结果:
从结果可以看出对赋值后的对象obj2进行改变,原对象obj1的值也进行了改变,原因就是赋值后的对象obj2赋值的是原对象obj1的栈内存地址,他们指向的是同一个堆内存数据,所以对赋值后的对象obj2对数据进行操作会改变了公共的堆内存中的数据,所以原对象的值也改变了。反之亦然,对原对象进行改变,obj2的值也会发生改变
var obj1 ={ name:'jack', age:25, hobby:['tennis','swiming','basketball'] } var obj2 = obj1 obj1.name = 'rose' obj1.hobby[1] = 'read' console.log('obj1.name',obj1.name) console.log('obj2.name',obj2.name) console.log('obj2.hobby',obj2.hobby) console.log('obj1.hobby',obj1.hobby)
结果同上
2.浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原对象属性值的一份精准拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
有点抽象,来看个例子,该例子也是手写浅拷贝的方法
var obj1 ={ name:'jack', age:25, hobby:['tennis','swiming','basketball'] } var obj3 = shallowCopy(obj1) function shallowCopy (src){ var newObj = {}; for(var prop in src ){ console.log(prop) if(src.hasOwnProperty(prop)){ newObj[prop] = src[prop] } } return newObj } obj3.name = 'rose' obj3.hobby[1] = 'read' console.log('obj1',obj1) console.log('obj3',obj3)
结果:
obj3改变了基本类型的值name,并没有使原对象obj1的name改变,obj3改变了引用类型的值,导致原对象的值也改变了
操作 | 是否指向同一个堆内存地址 | 基本数据类型 | 引用数据类型 |
赋值 | 是 | 改变会使原数据改变 | 改变会使原数据改变 |
浅拷贝 | 否 | 改变不会使原数据改变 | 改变会使原数据改变 |
深拷贝 | 否 | 改变不会使原数据改变 | 改变不会使原数据改变 |
总结:
赋值就是对原对象的栈内存地址进行复制
浅拷贝是对原对象的属性值进行精准复制,那么对如果原对象的属性值是基本类型那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候回修改到原对象。
因此一般对无引用类型的属性的兑现拷贝的时候使用浅拷贝就行,对复杂对象包含引用类型属性的时候使用深拷贝
四、浅拷贝的实现方式
1.Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
原对象属性中包含引用类型:进行了浅拷贝,拷贝了原对象属性值,所以拷贝的对象改变的时候原对象的引用类型也改变
var obj1 ={ name:'jack', age:25, hobby:{ ball:'tennis' } } let obj2 = Object.assign({},obj1) obj2.hobby.ball = 'basketball' console.log('obj1',obj1.hobby.ball) //basketball console.log('obj2',obj2.hobby.ball) //basketball
原对象属性中不包含引用类型的时候等价于深拷贝,因为不包含引用类型的时候是对属性值的拷贝也就是对基本类的值的复制,也就是值引用,所以对拷贝的对象改变不会影响到原对象,也就等价于深拷贝
var obj1 ={ name:'jack', age:25, } let obj2 = Object.assign({},obj1) obj2.name = 'rose' console.log('obj1',obj1.name) //jack console.log('obj2',obj2.name) //rose
对于数组的浅拷贝
2..Array.prototype.slice()
var arr = ['jack',25,{hobby:'tennise'}]; let arr1 = arr.slice() arr1[2].hobby='rose' arr1[0]='rose' console.log( arr[2].hobby) //rose console.log( arr[0]) //jack
3.Array.prototype.concat()
var arr = ['jack',25,{hobby:'tennise'}]; let arr1 = arr.concat() arr1[2].hobby='rose' arr1[0]='rose' console.log( arr[2].hobby) //rose console.log( arr[0]) //jack
4.解构
let aa = { age: 18, name: 'aaa', address: { city: 'shanghai' } } let bb = {...aa}; bb.address.city = 'shenzhen'; console.log(aa.address.city); // shenzhen
五、深拷贝的实现方法
1.JSON.parse(JSON.stringify())
var arr = ['jack',25,{hobby:'tennise'}]; let arr1 = JSON.parse(JSON.stringify(arr)) arr1[2].hobby='rose' arr1[0]='rose' console.log( arr[2].hobby) //tennise console.log( arr[0]) //jack
可见对拷贝对象进行改变不会影响原对象,原理就是用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
这种方式的缺点是当对象里面有函数的话,深拷贝后,函数会消失
2.手写递归函数实现深拷贝
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
var obj = { //原数据,包含字符串、对象、函数、数组等不同的类型 name:"test", main:{ a:1, b:2 }, fn:function(){ }, friends:[1,2,3,[22,33]] } function copy(obj){ let newobj = null; //声明一个变量用来储存拷贝之后的内容 //判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可, //由于null不可以循环但类型又是object,所以这个需要对null进行判断 if(typeof(obj) == 'object' && obj !== null){ //声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存 newobj = obj instanceof Array? [] : {}; //循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数 for(var i in obj){ newobj[i] = copy(obj[i]) } }else{ newobj = obj } console.log('77',newobj) return newobj; //函数必须有返回值,否则结构为undefined } var obj2 = copy(obj) obj2.name = '修改成功' obj2.main.a = 100 console.log(obj) console.log(obj2)
3.借助第三方库lodash
// 安装lodash npm i --save lodash // 引入lodash var _ = require('lodash'); var obj1 ={ name:'jack', age:25, } let obj2 =_.cloneDeep(obj1) obj2.name = 'rose' console.log('obj1',obj1.name) //jack console.log('obj2',obj2.name) //rose