两者区别如下:
深拷贝和浅拷贝是针对对象属性为对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个,而对于对象或者引用数据来说在进行浅拷贝时,只是将对象的引用复制了一份,也就内存地址,即两个不同的变量指向了同一个内存地址,那么在改变任一个变量的值都是该变这个内存地址的所存储的值,所以两个变量的值都会改变。
一、浅拷贝
- 浅拷贝:对于基本类型数据来说,拷贝的是值;对于引用数据来说,拷贝了地址,因此拷贝后的对象和原对象会共用一个内存地址,因此,属性值会同步变化(第二层往后的属性)
- 浅拷贝的方法:手写浅拷贝、Object.assign()、拓展运算符
1、手写一个浅拷贝
- 原始对象的一级属性不会被修改,但是二级往后的属性会被浅拷贝修改
function shallowClone(o) { let newObj = {}; for (let key in o) { newObj[key] = o[key]; } return newObj; } let obj = { id:'007', name:'xxx', hobby:['eat','sleep','play games'], foods:{ breakfast:'milk', lunch:'rice', dinner:'fish' } } let shallowObj = shallowClone(obj); shallowObj.id = '110'; //此时原始obj的id不会被改变,因为id是一级属性 shallowObj.hobby[2] = 'study'; //原始obj的hobby中第三个会被改变,因为属于二级属性 shallowObj.foods.dinner = 'apple'; //原始obj的foods中的dinner也会被改变 console.log(shallowObj); console.log(obj);
结果如下:
2、Object.assign()实现浅拷贝
当对象中只有一层时,使用Object.assign()可以深拷贝。
let obj = { id:'007', name:'xxx', hobby:['eat','sleep','play games'], foods:{ breakfast:'milk', lunch:'rice', dinner:'fish' } } let newObj = Object.assign({},obj); newObj.name = 'xyz'; //不会改变原始对象 newObj.foods.lunch = 'none'; //会改变 console.log(obj); console.log(newObj);
结果如下:
3、展开运算符…实现浅拷贝
数组使用拓展运算符实现浅拷贝时,第一层的属性不会改变即newObj[0] = ‘drink’ 不会改变原始数组了
let obj = { id:'007', name:'xxx', hobby:['eat','sleep','play games'], foods:{ breakfast:'milk', lunch:'rice', dinner:'fish' } } let newObj = {...obj }; newObj.id = '120'; //不会改变原始对象 newObj.hobby[0] = 'drink'; //会改变原始对象 console.log(newObj); console.log(obj);
结果为:
二、浅拷贝
对于引用数据类型来说,就是拷贝原始对象的所有属性与方法,在内存中重新开辟一块内存空间进行存储
1、手写一个深拷贝
(想直接看深拷贝的可以跳过,这里写出来是为了说明当属性值为数组时,直接递归已经不合适了)
这里:对于对象中属性值为引用数据类型的(数组,对象),采用递归深拷贝的方法。
但是这种方法对于对象中属性值为数组的对象来说,不好用
let obj = { id: '100', name: '小栗旬', hobby: ['drink', 'eat', 'play games'], address: { city: '北京市', area: '海淀区' } } // 手写一个深拷贝 function deepClone(obj) { let newObj = {}; for (let key in obj) { // 如果是引用数据类型,就递归深拷贝 if (typeof obj[key] === 'object') { newObj[key] = deepClone(obj[key]); } else { newObj[key] = obj[key]; } } return newObj; } let deepObj = deepClone(obj); deepObj.id = '10086'; //完全不影响原始对象 deepObj.name = '张学友'; deepObj.hobby[0] = 'study'; //完全不影响 deepObj.address.city = '上海市'; //完全不影响 deepObj.address.area = '静安区'; //完全不影响 console.log(deepObj); console.log(obj);
对于obj中的hobby来说,不好用,结果为:
完整版深拷贝:对于属性值为数组等非键值对的深拷贝,需要先判断是否为数组
function deepClone(obj) { // 如果传入的是基本类型,则直接返回 if(typeof obj !=='object') { return obj; } let newObj = Array.isArray(obj)?[]:{}; for(let key in obj) { //保证key不是Object原型上的属性,而是属于obj自身 if(obj.hasOwnProperty(key)) { //如果obj[key]还是对象,则递归,否则(是数组或基本类型)复制 if(obj[key] && typeof obj[key] === 'object') { newObj[key] = deepClone(obj[key]); }else{ newObj[key] = obj[key]; } } } return newObj; } let origin1 = null; let origin2 = { id:'008', name:'LULULU', hobby:['吃饭','睡觉','打游戏'], address:{ city:'北京市', area:'朝阳区' } } let deepObj1 = deepClone(origin1); console.log(origin1,deeoObj1); //当拷贝的原始对象是null时,会返回null的 let deepObj2 = deepClone(origin2); deepObj2.id = '66666'; deepObj2.name = 'MARK'; deepObj2.hobby[2] = '学习'; deepObj2.address.city = '上海市'; deepObj2.address.area = '静安区'; console.log(deepObj2); console.log(origin2);
当拷贝空对象时,也会返回空对象,当属性值为数组时,也会进行深拷贝,完全符合要求
结果为:
2、JSON.parse(JSON.stringify(obj))
使用JSON.stringify(obj)将对象转化为字符串,再使用JSON.parse()将字符串转化为JSON对象。
let obj = { id:'008', name:'LULULU', hobby:['吃饭','睡觉','打游戏'], address:{ city:'北京市', area:'朝阳区' } } let newObj = JSON.parse(JSON.stringify(obj)); newObj.name = 'kakaka'; newObj.hobby[0] = '逛街'; newObj.address.area = '海淀区'; console.log(newObj); console.log(obj);
结果为:
3、函数库lodash提供的cloneDeep函数
npm i lodash 安装依赖 import _ from 'lodash' 导入依赖 var newObj = _.cloneDeep(obj); 使用深拷贝