Fork me on GitHub

JS中深拷贝与浅拷贝

a15b4afegy1fmvjaacybej21hc0u0h0r.jpg

拷贝

拷贝,就像现在电脑前的你用鼠标右键一个文件,然后复制、粘贴,就是一次拷贝。

拷贝是为了得到这个文件或者数据的副本,既然是副本,就不能影响到本体。
但是在js中,我们该如何拷贝呢?是直接用‘=’来进行赋值吗?

首先考虑下面代码:

1
2
3
4
5
6
7
8
9
10
var a = 10;
var b = a;
console.log(a); //10
console.log(b); //10
//可以看到 b通过赋值获得了a的值。
//下面我们需要试一下 b会不会影响到a?
b = 20;
console.log(a); //10
console.log(b); //20
//可以看到 a和b是互不影响的,b的值变化后,a并没有跟着变化

上面这个代码,我在注释中做了解释。那么为什么呢?
这是因为,在js中,基础数据类型的值是直接在栈内存中存储的。
引用数据类型是保存到堆内存中的,变量保存的是对象的内存地址,而不是值。
举个栗子:
基本数据类型比较简单,存储小,就像一个苹果,你直接就拿着了,可以吃,可以用。
而引用数据类型就比较复杂了,存的东西也多,不能一只手拿着的那种,就好像你的女朋友,但是你用的时候怎么办呢?这时候你会发现你有她的手机号,你可以通过这个联系她。这个手机号就相当于引用数据类型的地址。
这就是js的两种数据了。
有没有懂呢?没有的话一定要告诉我!!

  • 下面是一些扩展,对于不了解数据类型的同学:
    • 值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
    • 引用数据类型:对象(Object)、数组(Array)、函数(Function)。
    • 注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。

在上面一段解释之后,我们继续主题:
我们已经知道了js中有两种数据类型,那么引用类型的也可以这样子拷贝吗?
我们试一下!!
考虑下面的代码:

1
2
3
4
5
6
7
8
9
var lilei = {
age:22,
name:'lilei'
}
var lilei2 = lilei;
// 和之前一样的操作。下面开始尝试更改一下
lilei2.age = 24;
console.log(lilei); //24
console.log(lilei2); //24

从这个代码块,我们测试后可以看到,lilei和lilei2的age都变为了24,lilei2把lilei的年纪给改了,那么我们失败了。。。
但是不要泄气!
先给你5分钟想一想,我们该如何拷贝–对象呢?

浅拷贝

首先还是这个例子:

1
2
3
4
var lilei = {
age:22,
name:'lilei'
}

因为这个一个对象,所以我们可以考虑,创建一个构造函数,来遍历对象中key,然后添加他们到新的对象中。
总结的话,就是通过遍历把里面的数据取出来放进一个新的对象里面。

1
2
3
4
5
6
7
function clone(obj){
var newObj = {};
for(var key in obj){
newObj[key]=obj[key]
}
return newObj;
}

然后让想要克隆的对象,调用这个构造函数,参数就是克隆本体

1
2
3
4
var lilei2=clone(lilei)
lilei2.age = 24;
console.log(lilei);
console.log(lilei2);

然后,我们到控制台去查看结果。结果正如我们所料!lilei2的age被改变为24,而lilei的age还是22.
但是,请再深入思考一下。。。这样真的就可以了吗?

下面考虑一下,把lilei变为更复杂的结构,内嵌对象,数组以及null。
为什么要考虑null呢?因为我们要用typeof判断数据类型,而null会被判断为对象。
可以尝试下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
var lilei = null
function clone(obj){
var newObj = {};
for(var key in obj){
newObj[key]=obj[key]
}
return newObj;
}
//改变
var lilei2=clone(lilei)
console.log(lilei);
console.log(lilei2);

会发现:null变成了{}。
然后让我们考虑下面的代码:

1
2
3
4
5
6
7
8
9
10
var lilei = {
name:'lilei',
age:22,
address:{
prov:"河南",
city:"郑州",
area:"金水区"
},
score:[100,90,80]
}

然后试着还是用上面的构造函数,写一写改变,看一看结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function clone(obj){
var newObj = {};
for(var key in obj){
newObj[key]=obj[key]
}
return newObj;
}
//改变
var lilei2=clone(lilei)
lilei2.age = 24;
lilei2.address.area="饮水区";
lilei2.score[2]=100;
console.log(lilei);
console.log(lilei2);

仔细看一看哪些改变了,哪些没有改变呢?
可以看到,score[2]和address里的area,lilei也跟着改变了,这里我们可以看到:
这是因为,浅拷贝没有复制内嵌的对象或数组,而是复制了地址,当内容改变,引用了这个地址的都会发生改变。
** 浅拷贝:如果包含内嵌的对象或数组,则不再复制副本 ** 由此引出深拷贝:
** 深拷贝:如果包含内嵌的对象或数组,也复制副本 ** 那么什么方法可以实现深拷贝呢?可以改造以下构造函数。

深拷贝的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function clone(obj){
//针对传过的是null的时候
if(obj===null){
return null;
}
//传来的是数组,通过slice返回新数组
if({}.toString.call(obj)==="[object Array]"){
var newArr=[];
newArr=obj.slice();
return newArr;
}
//上面两种排除后,剩下是对象
var newObj={};
for(var key in obj){
if(typeof obj[key]!=="object"){
//如果原对象中当前属性值是原始类型
newObj[key]=obj[key]
}else{
newObj[key]=clone(obj[key])
}
}
return newObj
}
深拷贝还有一种比较简单的方式,通过JSON.parse()和JSON.stringify()序列化和反序列化。这种还是比较简单的,看一下是不是null就可以了,不是null就可以用这种。

写到这里的时候,我发现浅拷贝相对于深拷贝来说,没有什么意义了,单纯的修改一层,然后还要起个名字,感觉和深拷贝不太对称。一家之言,欢迎探讨。
好了,结束。大家可以测试一下。
如果有误,或是交流,可以留言或者发邮件。再见!

坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束感谢您的阅读 ------