深拷贝VS浅拷贝javascript
本文主要对深拷贝&浅拷贝的解释及实现作一下简单记录。原文连接,欢迎star。html
之因此会有深拷贝与浅拷贝之分,是由于不一样数据类型的数据在内存中的存储区域不同。java
堆和栈是计算机中划分出来用来存储的区域,其中堆(heap)则是动态分配的内存,大小不定也不会自动释放;而栈(stack)为自动分配的内存空间,它由系统自动释放。存放在栈内存中的简单数据段,数据大小肯定,内存空间大小能够分配,是直接按值存放的,因此能够直接访问。git
众所周知,JavaScript中的数据分为(基本类型和引用类型)。五种基本类型(boolean
,number
,undefined
,null
,string
,)的数据的原始值是大小肯定不可变的,因此放在栈内存中;而引用类型(object)是放在堆内存中的,对应赋值的变量只是一个存放在栈内存中的指针,也就是一个指向引用类型存放在堆内存中的地址。github
引用类型是引用的比较,例如数组
// a与b是两个引用类型,但其指针指向的是两个不一样的引用 let a = [1,2,3]; let b = [1,2,3]; a===b; //false // 引用类型的赋值操做,b的指针也是指向与a一样的引用 let a = [1,2,3]; let b = a; a===b; //true
而基本类型间的比较是值的比较,例如函数
let a = 1; let b = 1; a===b; //true let a = 1; let b = a; a===b; //true
在对基本类型进行赋值操做的时候实际是传值,即在栈内存中新开一个区域给新的变量,而后再把值赋给它。因此基本类型赋值的变量是两个互相独立变量。post
let a = 10; let b = a; b++; console.log(a); //10 console.log(b); //11
而引用类型的赋值操做是传址,只是在栈内存中新增了一个变量,同时赋值给这个变量的只是保存在堆内存中对象的地址,也就是指针的指向。所以这两个变量的指针指向同一个地址,相互操做也就会有影响。this
let a = {}; let b = a; a.name = 'slevin'; console.log(b.name); //'slevin' console.log(a.name); //'slevin' console.log(a===b); //true
须要注意的是,引用类型的赋值并不算是浅拷贝,由于赋值操做只至关因而引用,两个变量仍是指向同一对象,任何一方改变了都会影响到另外一方;但浅拷贝出来的变量与源变量已经不一样,在不包含子对象的状况下(此状况即为深拷贝),一方改变不会影响到另外一方。以下赋值操做:spa
let a = {}; let b = a; b.name = 'slevin'; console.log(a.name); //'slevin';对b操做影响到了a console.log(a===b); //true;由于二者指向同一个对象
自定义实现方法:
// 浅拷贝的方法 function shallowCopy(srcObj) { let copy = {}; for (let key in srcObj) { //只拷贝自身的属性,__proto__上继承来的属性不作拷贝,也可去掉这个限制 if (srcObj.hasOwnProperty(key)) { copy[key] = srcObj[key]; } } return copy; } let obj = { name: 'slevin', age: 18, language: { 'english': 'good', 'mandarin': 'wonderful', } } let copy = shallowCopy(obj); copy.age = 28; console.log(obj.age); // 18; 并无改变源对象 console.log(obj === copy); //false;二者指向不是同一个对象
利用Object.assign(target,...sources)
可将一个或多个源对象上可枚举属性的值复制到目标对象,并返回目标对象。但注意,只能作一层属性的浅拷贝。
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = Object.assign({},obj); //不会改变源对象 copy.age = 22; //会改变源对象 copy.language.english = 'bad'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
对于数组来讲,也可使用Array.prototype.slice()
方法和Array.prototype.concact()
方法
let obj = [1,2,['a','b','c']] let copy = obj.slice(); // 不会改变源对象 copy[0]=111; // 会改变源对象 copy[2][0]='aaa'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
简单来讲,深拷贝就是把对象以及对象的子对象进行拷贝。由于浅拷贝只复制了一层对象的属性,而若是对象的数值也为引用类型,那么拷贝过来依然是个引用地址,在拷贝对象上对子对象的值进行操做会改变原始数据中的值。
例如上面obj
浅拷贝获得copy对象
,若是在copy
对象上改变子对象copy.language
上的属性值,会影响到源对象obj
:
copy.language.english = 'bad'; copy.language.mandarin = 'bad'; console.log(obj.language); // {'english': 'bad','mandarin': 'bad',}
那么如何深拷贝呢?思路就是递归调用刚刚的浅拷贝,遍历全部值为引用类型的属性,而后依次赋值给另一个对象便可。
方法一,自定义实现:
/**将源对象source深度合并到目标对象target上
*/
function deepCopy (source,target={},deep=true) { for (let key in source){ // 深拷贝,并且只拷贝自身属性.(默认传入的source为对象) if (deep && source.hasOwnProperty(key)){ if (typeof(source[key])==='object'){ // // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,不然递归会出错的 // if (!(target[key] instanceof Object) && (source[key] instanceof Object)){ // target[key] = {}; // } // // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,不然递归会出错的 // if (!Array.isArray(target[key]) && Array.isArray(source[key])) { // target[key] = []; // } // 上面的代码能够简化为如下: target[key] = Array.isArray(source[key]) ? [] : {}; // 递归执行拷贝 deepCopy(source[key],target[key],true); } else { target[key] = source[key]; } } } return target; } let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test:[1,2,3] } } let copy = deepCopy(obj); copy.language.test[0] = 111; console.log('copy:',copy); //111 改变目标对象的子对象属性值 console.log('obj:',obj); //1 对应源对象上没有改变
利用JSON.parse(JSON.stringify(copyObj))
方法
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] } } let copy = JSON.parse(JSON.stringify(obj)) copy.language.test[0]=111; console.log('copy===obj:',copy===obj);//false console.log('copy.language.test[0]:',copy.language.test[0]);//111 console.log('obj.language.test[0]:',obj.language.test[0]);//1
但要注意,此方法有两个缺点:
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = JSON.parse(JSON.stringify(obj)); console.log('copy===obj:',copy===obj);//false console.log('obj.fn:',obj.fn); //fn(){}... console.log('copy.fn:',copy.fn); //undefined
最后,再补一张引用类型的赋值操做、浅拷贝深拷贝对比图,加深印象