什么是赋值?什么是浅拷贝?什么是深拷贝呢?
首先我们先来看一个例子:
let a = {title: '深拷贝'};
let b = a;
a.title = '浅拷贝';
我们声明了一个名为a的对象,并把a赋值给b,然后再修改对象a里面的title属性,然后我们分别打印对象a和b的值,让我们来看看输出结果分别是什么
从输出结果中我们可以看到,我们虽然只修改了对象a的属性值,但是对象b的属性值也跟着一起被修改了,这是为什么呢?因为如果我们仅仅是把对象a在内存中的储存地址直接共享给了对象b,a和b是共用同一个地址,虽然看起来是两个对象,但其实就是同一个对象,只是名字不同而已。这就是赋值。
就好比在现实中a同学有一台电脑,此时b同学也需要一台电脑,但b同学没有电脑,因此跑去问a同学借电脑,此时a同学和b同学就共用一台电脑,假如a同学把电脑里面的数据改变了,那b同学那里的数据也会被改变。
那如果是想复制对象a中的属性值,创建一个新对象,并不希望把对象a的储存地址也拷贝过来,要如何操作呢?
这里我们可以用JS的‘展开语法’,将对象a里面的属性值存入对象b中。
let a = {title: '深复制'};
let b = {...a};
a.title = '浅复制';
此时我们再来看看控制台输出的结果:
这时候我们修改对象a中的title属性值就不会影响到b对象了。这就是浅拷贝。
这就相当于我们复制了对象a中的数据但是并没有复制对象a的储存地址,因此对象a和b是两个完全不同的对象,我们在对其中一个进行修改数据时并不会影响到第二个对象。
让我们再来看看其他几种浅拷贝的方法:
1. Object.assign(target, ...sources)。 target:目标对象, sources:源对象
方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
let obj = {name: "Joker", title: "my name is Joker"}
let newObj = Object.assign({}, obj); //Object.assign({}, 对象名)
newObj.name = "newJoker";
console.log(newObj);
console.log(obj)
2. for in遍历对象
let obj = {name: "Joker", title: "my name is Joker"};
let newObj = {};
for (const key in obj) {
console.log(key);
newObj[key] = obj[key] //遍历拿到obj对象中的所有属性名,并把值复制到newObj对象中
}
newObj.name = "newJoker";
console.log(newObj);
console.log(obj)
3. 对象属性的赋值
let obj = {name: "Joker", title: "my name is Joker"};
let newObj = {
name: obj.name,
title: obj.title
};
newObj.name = "newJoker";
console.log(newObj);
console.log(obj)
当我们使用以上几种方法对新对象进行修改的时候,都不会对原数据造成影响。
当我们明白了普通赋值与浅拷贝的区别之后,再来看看浅拷贝跟深拷贝的区别。以下就是浅拷贝的方式,但是如果我们拷贝的对象中包含子对象的时候,当我们改变子对象中的值,原数据中的子对象值也会被一同改变。而深拷贝即使有子对象,也不会受相互影响。
let obj = {name: "Joker", title: "my name is Joker", objson: {nameSon: 'Joker'}};
let newObj = {...obj};
newObj.objson.nameSon = "newJoker";
console.log(newObj);
console.log(obj)
实现深拷贝的方法:使用递归的方式封装函数。
let obj = {name: "Joker", title: "my name is Joker", objSon: {nameSon: "Joker"}};
function copy(object) {
let res = {};
for (const key in object) {
res[key] = typeof object[key] === "object" ? copy(object[key]) : object[key]
}
return res;
}
let newObj = copy(obj);
newObj.objSon.nameSon = "new Joker";
console.log(newObj);
console.log(obj);
这时候我们修改新对象中的数据就不会影响到原对象中的数据啦。这就是深拷贝。
那么深拷贝跟浅拷贝的原理到底是什么呢?在我们的系统后台里面,有两个内存空间,一个叫栈内存,一个叫堆内存。
我们先来谈谈基本数据类型的储存原理:当我们声明一个基本数据类型的时候,系统会把名和值都存入栈内存中,例如:let name=“Joker”; 那么这个name和name的值都会被存储到栈内存中。
如果我们将name赋值给另外一个变量(title = name),那么系统会默认开辟一个内存空间,因此如果我们修改其中一个的值,另外一个就不会受到影响。
但引用数据类型则不同。当我们声明的是一个引用数据类型的时候,系统会把名存入栈内存,而把值存入堆内存,栈内存会提供一个引用的地址并指向堆内存中的值。
当我们进行title = name的时候,其实是把name的引用地址复制给了title。换句话说,name和title这时候都指向同一个内存地址,因此我们在对其中一个进行值的修改的时候,另外一个也会跟着修改。这就是浅拷贝。如果要实现引用类型的深拷贝,就要在堆内存中也开辟一个新的内存地址,这样二者之间才不会相互影响。