JavaScript最完整的深浅拷贝实现方式详解
作者:Beannnnnnn
深浅拷贝:
内存中一共分为栈内存和堆内存两大区域,所谓深浅拷贝主要是对js引用类型数据进行拷贝一份,浅拷贝就是引用类型数据相互赋值之后,例obj1=obj2;如果后面的操作中修改obj1或者obj2,这个时候数据是会进行相应的变化的,因为在内存中引用类型数据是存储在堆内存中,堆内存中存放的是引用类型的值,同时会有一个指针地址指向栈内存,两个引用类型数据地址一样,如果其中一个发生变化另外一个都会有影响;而深拷贝则不会,深拷贝是会在堆内存中重新开辟一块空间进行存放;
简单来说就是B复制了A,如果A发生了改变,如果B随之变化,那么是浅拷贝,如果B并没有发生变化,则是深拷贝。
基本类型拷贝
let a = 1; let b = a; b = 2; console.log(a);//1 console.log(b);//2
a,b都是属于基本类型,基本类型的复制是不会影响对方的,因为基本类型是每一次创建变量都会在栈内存中开辟一块内存,用来存放值,所以对基本类型进行拷贝是不会对另外一个变量有影响的,属于深拷贝。
数组拷贝
concat() slice()
// concat() let list = ['a','b','c']; let list2 = list.concat(); list2.push('d') console.log(list);//['a','b','c'] console.log(list2);//['a','b','c','d'] // slice() let list = ['a','b','c']; let list2 = list.slice(); list2.push('d') console.log(list);//['a','b','c'] console.log(list2);//['a','b','c','d']
上面两种方法只能实现数组类型里面的单层深拷贝,如果是多层无法实现深拷贝。
//二维数组 let list = ['a','b','c',['d','e','f']]; let list2 = list.concat(); list2[3][0] = 'a'; console.log(list); console.log(list2);
可以看到原二维数组的值也发生了变化,说明没有实现深拷贝。
由此总结,concat和slice只能实现一维数组的深拷贝,不能对多维数组进行深拷贝,所以并不是一个完美的深拷贝方式。
对象拷贝
new Object()
let a = {id:1,name:'a',obj:{id:999}}; let b = new Object(); b.id = a.id; b.name = a.name; b.obj = a.obj; a.name = 'b'; a.obj.id = 888; console.log(a); console.log(b);
a.name更改并没有影响到b.name,好像可以看作深拷贝,但第二层obj里面里面的id随之更改了,因此其实并不是深拷贝。
Object.assign
let a = {id:1,name:'a',obj:{id:999}}; function fun(obj){ let o = {}; Object.assign(o,obj); return o; } let a2 = fun(a); a2.name ='a2'; a2.obj.id = 888; console.log(a); console.log(a2);
以上两种方法,对于一层对象都能实现深拷贝,但对于多层对象则无法实现,因此也不是一种完美的深拷贝方式。
JSON.parse(JSON.stringify( ))
let a = { name : 'a', age : 20, obj : {id:999}, action : function(){ console.log(this.name); } } let b = JSON.parse(JSON.stringify(a)); a.name = 'b'; a.obj.id = 888; console.log(a); console.log(b);
单层对象name和多层对象obj.id的改变都没影响到原对象,因此是一个比较完美的深拷贝,但是能看到function好像并没有被拷贝。
可以看出这个方法对于一层和多层都能实现深拷贝,但是这个方法的缺陷是不能拷贝Function,所以在使用时,一定要注意数据类型。
递归
let a = { name:'a', skin:["red","blue","yellow",["123","456"]], child:{ work:'none', obj:{ id:999 } }, action:function(){ console.log(this.name); } } //封装的递归方法 function copyWid(obj){ let newObj = Array.isArray(obj)?[]:{}; for (var i in obj){ if(typeof obj[i] === 'object'){ //判断是不是对象(数组或对象) newObj[i] = copyWid(obj[i]) //递归解决多层拷贝 }else{ newObj[i] = obj[i] } } return newObj; }; let b = copyWid(a); b.child.obj.id =888; b.skin[3][0] = "pink"; console.log(a); console.log(b);
可以看到,无论是多层的对象还是多层的数组,都能实现深拷贝,而且能拷贝函数,这个是目前来说最完美的深拷贝方法。
通过这个递归能实现一个比较完美的深拷贝,能弥补上述提到的所有方法中的缺点。
展开运算符
let a = {name:'a',id:99};//如果是数组[xx,xx,xx],{...a}需要改成[...a] let b ={...a}; //[...a] a.id =88; console.log(a); console.log(b);
这个方法能实现对象数组单层时的深拷贝,但是多层无法实现深拷贝。
以上这么多方法,最优解应该属于JSON和递归的方法(递归解决了JSON无法拷贝函数方法的问题),但是根据需求数据类型的不同,可以选择更简单的方式。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!