拷贝
拷贝,就像现在电脑前的你用鼠标右键一个文件,然后复制、粘贴,就是一次拷贝。
拷贝是为了得到这个文件或者数据的副本,既然是副本,就不能影响到本体。
但是在js中,我们该如何拷贝呢?是直接用‘=’来进行赋值吗?
首先考虑下面代码:
1 | var a = 10; |
上面这个代码,我在注释中做了解释。那么为什么呢?
这是因为,在js中,基础数据类型的值是直接在栈内存中存储的。
引用数据类型是保存到堆内存中的,变量保存的是对象的内存地址,而不是值。
举个栗子:
基本数据类型比较简单,存储小,就像一个苹果,你直接就拿着了,可以吃,可以用。
而引用数据类型就比较复杂了,存的东西也多,不能一只手拿着的那种,就好像你的女朋友,但是你用的时候怎么办呢?这时候你会发现你有她的手机号,你可以通过这个联系她。这个手机号就相当于引用数据类型的地址。
这就是js的两种数据了。
有没有懂呢?没有的话一定要告诉我!!
- 下面是一些扩展,对于不了解数据类型的同学:
- 值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
- 引用数据类型:对象(Object)、数组(Array)、函数(Function)。
- 注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。
在上面一段解释之后,我们继续主题:
我们已经知道了js中有两种数据类型,那么引用类型的也可以这样子拷贝吗?
我们试一下!!
考虑下面的代码:
1 | var lilei = { |
从这个代码块,我们测试后可以看到,lilei和lilei2的age都变为了24,lilei2把lilei的年纪给改了,那么我们失败了。。。
但是不要泄气!
先给你5分钟想一想,我们该如何拷贝–对象呢?
浅拷贝
首先还是这个例子:
1 | var lilei = { |
因为这个一个对象,所以我们可以考虑,创建一个构造函数,来遍历对象中key,然后添加他们到新的对象中。
总结的话,就是通过遍历把里面的数据取出来放进一个新的对象里面。
1 | function clone(obj){ |
然后让想要克隆的对象,调用这个构造函数,参数就是克隆本体
1 | var lilei2=clone(lilei) |
然后,我们到控制台去查看结果。结果正如我们所料!lilei2的age被改变为24,而lilei的age还是22.
但是,请再深入思考一下。。。这样真的就可以了吗?
下面考虑一下,把lilei变为更复杂的结构,内嵌对象,数组以及null。
为什么要考虑null呢?因为我们要用typeof判断数据类型,而null会被判断为对象。
可以尝试下面的代码:
1 | var lilei = null |
会发现:null变成了{}。
然后让我们考虑下面的代码:
1 | var lilei = { |
然后试着还是用上面的构造函数,写一写改变,看一看结果:
1 | function clone(obj){ |
仔细看一看哪些改变了,哪些没有改变呢?
可以看到,score[2]和address里的area,lilei也跟着改变了,这里我们可以看到:
这是因为,浅拷贝没有复制内嵌的对象或数组,而是复制了地址,当内容改变,引用了这个地址的都会发生改变。
** 浅拷贝:如果包含内嵌的对象或数组,则不再复制副本 **
由此引出深拷贝:
** 深拷贝:如果包含内嵌的对象或数组,也复制副本 **
那么什么方法可以实现深拷贝呢?可以改造以下构造函数。
深拷贝的方法:
1 | function clone(obj){ |
深拷贝还有一种比较简单的方式,通过JSON.parse()和JSON.stringify()序列化和反序列化。这种还是比较简单的,看一下是不是null就可以了,不是null就可以用这种。
写到这里的时候,我发现浅拷贝相对于深拷贝来说,没有什么意义了,单纯的修改一层,然后还要起个名字,感觉和深拷贝不太对称。一家之言,欢迎探讨。
好了,结束。大家可以测试一下。
如果有误,或是交流,可以留言或者发邮件。再见!