前言
在开发项目的时候,发现用ES6的解构赋值和[].contant对数组进行新的赋值时,发现还是会影响到旧数组的数据。无奈之下,最后用了JSON.parse(JSON.stringify(obj))进行深拷贝。
先认识一下深拷贝和浅拷贝的定义
深拷贝:修改新变量的值不会影响原有变量的值。默认情况下基本数据类型(number,string,null,undefined,boolean)都是深拷贝。
浅拷贝:修改新变量的值会影响原有的变量的值。默认情况下引用类型(object)都是浅拷贝。
对深拷贝和浅拷贝有了简单的认识了,下面我们开始今天的旅程吧。
ES6的解构赋值
下面开看一个简单的例子:
var obj = {
name: "张三",
age: 18
}
var obj1 = {...obj}
obj1.age = 20
console.log(obj,obj1)
复制代码
打印结果:
很多伙伴就会说,这是深拷贝啊
那我们再看看一个例子:
var obj = {
name: "张三",
age: 18,
info: {
company: "周星星公司"
}
}
var obj1 = {
...obj
}
obj1.info.company = "朱茵公司"
console.log(obj, obj1)
复制代码
打印结果:
是不是很意外,是不是很惊喜。这是为什么,这不是深拷贝吗?why?
发现解构赋值出来的对象将原对象obj中的company的数据修改了,这样看还是浅拷贝。
Object.assign
这个之前有个伙伴跟我跟,他可以深拷贝和浅拷贝,我之前以为一直都是浅拷贝而已。
只要这样就可以进行深拷贝了:
Object.assign({},obj)
复制代码
确实这样能达到深拷贝,可以看下面一个例子:
var obj = {
name: "周星星",
age: 18
}
var obj1 = Object.assign({},obj)
obj1.age = 20
console.log(obj, obj1)
复制代码
打印结果:
这是为什么呢,因为我们重新定义了一个空对象{}
,在空数组里面添加新的属性,因为Object.assign中前对象会被后对象的值所覆盖。确实让我认识到了一个新写法。
但是,我们再看看这个例子:
var obj = {
name: "周星星",
age: 18,
info:{
company:"周星星的公司"
}
}
var obj1 = Object.assign({},obj)
obj1.info.company = "朱茵的公司"
console.log(obj, obj1)
复制代码
惊喜总是那么的意外啊。依旧是company
修改了,合着旧数据一起被改掉了,还是浅拷贝。
[].concat
依旧我们先来个例子:
var list = [1,2,3]
var list1 = [].concat(list)
list1[1] = 9
console.log(list, list1)
复制代码
打印结果:
有人看到这个第一眼也会说,这是个深拷贝,这可以解决问题,那我就用这个吧
那我们再来看看这个例子:
var list = [{
name: "周星星",
age: 18
}, {
name: "朱茵",
age: 16
}]
var list1 = [].concat(list)
list1[1].age = 9
console.log(list, list1)
复制代码
打印结果:
这是为什么啊,我明明给了个新数组了,改的也是新数组的数据,但是旧数据还是会变的呢,最终还是个浅拷贝
总结
- 如果所解构的原对象是一维数组或对象,其本质就是对基本数据类型进行等号赋值,那它们就是深拷贝;
- 如果是多维数组或对象,其本质就是对引用类型数据进项等号赋值,那它们就是浅拷贝;
结论:它们都是浅拷贝(因为它确实不能对多维数组或对象达到深拷贝的作用);
如果需要使用它们用作深拷贝,需要保证是一维的。
拓展-深拷贝方法
递归
原理:我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数就好了
let deepCopy = function (obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
let newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果obj的子属性是对象,则进行递归操作,否则直接赋值
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
复制代码
JSON.parse(JSON.stringify(obj))
为什么前面会说无奈之举才会有用JSON.stringify和JSON.parse进行深拷贝呢?
这行代码的运行过程,就是利用 JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;序列化的作用是存储和传输。(对象本身存储的是一个地址映射,如果断电,对象将不存在,所以要将对象的内容转换成字符串的形式再保存在磁盘上)
不过,这种实现深拷贝的方法有局限性,它只适用于一般数据的拷贝(对象、数组),有以下情况需要注意:
1.如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式;
{
let obj = {
age: 18,
date: new Date()
};
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
console.log(typeof obj.date); // object
console.log(typeof objCopy.date); // string
}
复制代码
2.如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};
{
let obj = {
age: 18,
reg: new RegExp('\w+'),
err: new Error('error message')
};
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
}
复制代码
3.如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;
{
let obj = {
age: 18,
fn: function () {
console.log('fn');
},
hh: undefined
};
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
}
复制代码
4.如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
{
let obj = {
age: 18,
hh: NaN,
isInfinite: 1.7976931348623157E+10308,
minusInfinity: -1.7976931348623157E+10308
};
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
}
复制代码
5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;
{
function Person(name) {
this.name = name;
}
let obj = {
age: 18,
p1: new Person('lxcan')
};
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
console.log(obj.p1.__proto__.constructor === Person); // true
console.log(objCopy.p1.__proto__.constructor === Object); // true
}
复制代码
6.如果对象中存在循环引用的情况也无法实现深拷贝
{
let obj = {
age: 18
};
obj.obj = obj;
let objCopy = JSON.parse(JSON.stringify(obj));
console.log('obj', obj);
console.log('objCopy', objCopy);
}
复制代码
以上,如果拷贝的对象不涉及上面的情况,可以使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝。