在开发中经常会遇到数据深浅拷贝的问题,如果运用不当的话很容易出现bug。对于深浅拷贝最直观的理解就是,浅拷贝数据之后原变量或新变量中任意一个值修改后另一个变量值也会被修改,深拷贝数据则不会出现这种情况。
一、按数据类型分析
1、基本数据类型
对于基本数据类型string、number、null、undefined、boolean,其真实数据存储在栈中,使用=赋值的过程即为深拷贝,所以不需要过多的关注
2、引用数据类型
对于引用数据类型array、object、function类型,在栈中存储的是一个地址,该地址指向堆中存储的真实数据。
在浅拷贝时,只是在栈中开辟一个空间,并拷贝原变量在栈中存储的地址,所以此时两个变量指向的真实数据是一样的,修改原变量的值新变量也会跟着变化。
在深拷贝时,不仅会在栈中开辟一个空间拷贝原变量在栈中存储的地址,而且会在堆中开辟一块空间存储原变量的真实数据,此时再修改原变量的值新变量不会再随着变化。
二、深浅拷贝方法(以下只针对引用数据类型的深浅拷贝进行列举)
1、浅拷贝
(1)直接赋值
let obj1 = {
name: 'zhangsan'
}
let obj2 = obj1
(2)使用es6...拓展符
值得注意的是,...会深拷贝第一层的非引用类型,array.slice、array.concat、array.from、Object.assign()也是同理
let obj1 = {
name: 'zhangsan',
class: [1, 2, 3]
}
let obj2 = { ...obj1 };
obj1.name = 'lisi';
obj1.class[0] = 4;
console.log(obj1, obj2); //{name: 'lisi', class: [4, 2, 3]}, {name: 'zhangsan', class: [4, 2, 3]}
(3)遍历对象,根据属性逐一赋值
shallowClone(obj) {
//typeof []返回值也是object
if (typeof obj !== 'object') return;
// 兼容对象和数组
var newObj = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
2、深拷贝
(1)使用JSON.parse(JSON.stringfy())暴力深拷贝
obj2 = JSON.parse(JSON.stringfy(obj1))
使用这种方法需要注意:时间对象会被转换为字符串格式;数据的中function、undefined序列化之后会被丢失掉;NaN、Infinity和-Infinity序列化之后会变成null;JSON.stringfy()只能序列化对象的可枚举的自有属性,如果obj中的对象是有构造函数生成的,使用这种方法深拷贝后会丢弃对象的constructor;无法正确拷贝引用数据类型中存在循环引用的情况。
(2)使用js库lodash中的_.cloneDeep()
let obj1 = {
name: 'zhangsan',
class: [1, 2, 3]
}
let obj2 = _.cloneDeep(obj1);
obj1.class[0] = 4;
obj1.name = 'lisi';
console.log(obj1, obj2); //{name: 'lisi', class: [4, 2, 3]}, {name: 'zhangsan', class: [1, 2, 3]
(3)使用递归进行深拷贝
deepClone(obj) {
if (typeof obj !== 'object') return obj;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果当前属性仍是对象则递归调用深拷贝
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}