袅袅青烟散 2019-06-29
要理解 JavaScript中浅拷贝和深拷贝的区别,首先要明白JavaScript的数据类型。JavaScript有两种数据类型,基础数据类型和引用数据类型。
js的基本类型:undefined,null,string,boolean,number,symbol(es6新增),保存在栈内存中
js的引用类型:Object类型, Array类型,Date类型,RegExp类型,Function类型,基本包装对象(Boolean类型,Number类型,String类型),单体内置对象(Global对象,Math对象),保存在堆内存空间中
1.1基本类型值
基本类型值是指在栈内存保存的简单数据段,在复制基本类型值的时候,会开辟出一个新的内存空间,将值复制到新的内存空间。
var a = 1; var b = a; a = 2; console.log(a);//输出2; console.log(b);//输出1;
var a = 1;
var b = a;
a = 2;
从上面例子看出,当一个变量的值是基本类型,把它复制给另一个变量,复制完成后改变它的值,不会影响已经复制了它的值的变量。
1.2引用类型值
引用类型值是保存在堆内存中的对象,变量保存的只是指向该内存的地址,在复制引用类型值的时候,其实只复制了指向该内存的地址。
var a = { name: 'Kitty', age: '20', sex: 'man' }; var b = a; a.name = 'Jack'; console.log(a);//输出{name: 'Jack',age: 20,sex: 'man'} console.log(b);//输出{name: 'Jack',age: 20,sex: 'man'}
var a = {name: ‘Kitty’,age: ‘20’,sex: ‘man’};
var b = a;
a.name = ‘Jack’;
从上面例子看出,当一个变量的值是引用类型值,把它复制给另外一个变量,复制的只是指向储存对象内存的地址,所以复制完成后,改变它的值,会影响复制了它的值的变量。
注意:如果有两个变量的值是引用类型值,就算它们的值完全相同,它们也是不相等的,因为它们指向的内存地址不同,例子:
当一个变量是对象,如果像上面那样直接将一个变量赋值给另一个变量,如果改变某个变量的值,另外一个变量也会跟着改变,如果我们不想发生这种情况,就需要写一个函数用来拷贝对象。
深拷贝和浅拷贝的示意图大致如下:
2.1 对象浅拷贝
var a = {name:'wanger'} var b = Object.assign({}, a) a===b // false b.name = 'zhangsan' a.name //'wanger'
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆,这时候a与b指向的是不同的栈对象,所以对b.name重新复制也不会影响到a.name。但是如果a.name是一个对象的引用,而不是一个字符串,那么上面的代码也会遇到一些问题,参考如下代码:
var a = {name:{firstName:'wang',lastName:'er'}} var b = Object.assign({}, a) a===b // false b.name.firstName = 'zhang' a.name.firstName //'zhang'
b.name.firstName又影响到了a.name.firstName,这是因为Object.assign()方法只是浅层拷贝,a.name是一个栈对象的引用,赋值给b时,b.name也同样是这个栈对象的引用,很多时候,我们不想让这种事情发生,所以我们就需要用到对象的深拷贝。
2.2 对象深拷贝
2.2.1 万能的for循环实现对象的深拷贝
var obj = { name: 'FungLeo', sex: 'man', old: '18' } var obj2 = copyObj(obj) function copyObj(obj) { let res = {} for (var key in obj) { res[key] = obj[key] } return res }
2.2.2 JSON.parse(JSON.stringify(objectToClone))
var obj = { name: 'FungLeo', sex: 'man', old: '18' } var obj2 = JSON.parse(JSON.stringify(obj))
2.2.3 扩展运算符实现对象的深拷贝
var obj = { name: 'FungLeo', sex: 'man', old: '18' } var { ...obj2 } = obj obj.old = '22' console.log(obj) console.log(obj2)
运行结果如下:
3.1 数组浅拷贝
var arr = ['old', 1, true, null, undefined]; var new_arr = arr.concat(); // 或者var new_arr = arr.slice()也是一样的效果; new_arr[0] = 'new'; console.log(arr); // ["old", 1, true, null, undefined] console.log(new_arr); // ["new", 1, true, null, undefined]
数组的浅拷贝,可用concat、slice返回一个新数组的特性来实现拷贝,但是如果数组嵌套了对象或者数组的话用concat、slice拷贝只要有修改会引起新旧数组都一起改变了,比如:
var arr = [{old: 'old'}, ['old']]; var new_arr = arr.concat(); arr[0].old = 'new'; new_arr[1][0] = 'new'; console.log(arr); // [{old: 'new'}, ['new']] console.log(new_arr); // [{old: 'new'}, ['new']]
如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。这种叫浅拷贝 。
深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
3.2 数组深拷贝
3.2.1 利用扩展运算符...对数组中嵌套对象进行深拷贝
var arr=[{a:1,b:2},{c:1,d:2}]; var arr2=[]; arr.forEach(item=>{ var {...obj}=item; arr2.push(obj); }) arr2[1].d=7 console.log(arr,arr2)
3.2.2 利用lodash库的cloneDeep方法
var arr=[{a:1,b:2},{c:1,d:2}]; var arr2=_.cloneDeep(arr) arr2[1].d=7; console.log(arr,arr2)
3.2.3 JSON.parse(JSON.stringify(objectToClone))
var arr=[{a:1,b:2},{c:1,d:2}]; var arr2=JSON.parse(JSON.stringify(arr)); arr2[1].d=7; console.log(arr,arr2)