继续研究基础知识,老菜鸟真的玩不起了,加油吧,腊肉!
初学的时候,没理解深浅拷贝,现在玩代码真的是力不从心,决定重新学习基础知识!
我们代码中常常这么写:
var a = '1234'
var b = a
b = '5678'
console.log(a, b) // '1234' '5678'
结果是: a是a,b是b,两个变量之间没有任何影响,一旦被赋值,就不会发生改变,但是看看数组如果也这么玩,会是什么结果?
var a = [1,2,3,4]
var b = a
b[0] = 5
console.log(a, b)
我们来看结果:
a和b两个数组都发生了变化,但是我们没有直接改动a啊,怎么a也发生了变化?对于我这个小学生来说,真是一头雾水。
今天趁着有时间,查了资料,发现用“堆栈”的概念去理解会更好记忆一些。
我们知道,js中数据类型,包括基本数据类型: string number boolean undefined null 以及ES6中的symbol还有起草的ES10中BigInt类型;第二类是引用数据类型:object对象 数组以及function等。
那么基本数据类型和引用数据类型,既然起了两个名字,那实现的方式肯定也不一样,顺着这个思路,我们来看看究竟是怎么回事:
我们简单的从“堆栈”的角度看看,堆,可以简单理解成是“程序员”给它分配的内存空间,“被动”被占用的,而栈是系统主动分配的空间。
基本数据类型的操作都是在栈中执行的,而引用数据类型则是在堆中操作的:
引用数据类型是怎么操作的呢?
我们的引用类型的数据是存放在堆中的,你看到的var a = [1,2,3,4]只是a 去堆中引用过来的,本质上a只具有显示成[1,2,3,4]的样子,并没有实际的操作能力。既然a都没有实际的能力,那b就更不用说了,同样也只是显示成[1,2,3,4]的样子罢了,不管对a操作还是对b操作,操作的都是堆内存中的实际数据。
来看看a和b数组是不是同一个东西:
var a = [1,2,3,4]
var b = a
console.log(a === b) // true
好吧,这个结果更加验证了,a和b只是显示堆中的数组[1,2,3,4].
再来看一个例子:
var a = [1,2,3,4]
var b = [1,2,3,4]
console.log(a === b) // false
why?为啥不相等了呢?画个图就明白了:
是不是清楚了?这种情况下,a和b分别去不同的堆内存中去取数据了,当然是两个不一样的东西了啊。
但是两个外形一样的字符串,是不是相等的?当然相等!没有涉及到更深层的堆,所以只是字面量上的比较,结果相等。
好,弄清楚了基本数据类型和引用数据类型的表现,现在开始看拷贝的问题:
再看一个例子:
var a = {
key1: '1-1',
key2: '1-2',
key3: [1,1,1]
}
var b = a // a赋值给b
function copy(obj) {
var result = {}
for (var key in obj) {
if (key && obj[key]) {
result[key] = obj[key]
}
}
return result
}
var c = copy(a)// a浅拷贝给c
b.key1 = '2-1'
c.key2 = '3-2'
b.key3[1] = 2
c.key3[2] = 3
console.log(a, b, c)
结果如下:
可以得到如下结论:
1、给b.key1重新赋值,会影响a.key1的值,但是给c.key2从新赋值,则不会影响a.key2的值,我们得出结论:对象的直接赋值给新对象,新的对象中某基本数据类型的数据还是指向原来对象的值,是同一个量,而通过浅拷贝的基本数据类型,则是一个新的量,与原对象中的数据无关;
2、无论是赋值,还是浅拷贝,引用类型的数据,改变一处,所有引用该数据的地方都会发生改变。这是因为浅拷贝只复制基本数据类型(没有下级数据)的属性,并不包括对象里面的为引用类型的数据。所以就会出现改变浅拷贝得到的c中的引用类型时,会使原来对象得到改变。
既然知道了原因,那我们现在可以考虑如何才能做到把c变成一个彻彻底底的不影响原来对象的独立对象呢?这正是深复制要做的事情,在浅复制的基础上,把下级数据也复制上就行啦:
function deepCopy(obj,result){
var result = result || {} // 局部变量result赋初值为接收的参数或者为一个空对象。
for(var key in obj){
if(typeof obj[key] === 'object'){ // 依次判断obj对象的属性是不是对象
result[key] = (Array.isArray(obj[key])) ? [] : {} // 判断要复制的项是对象还是数组
deepCopy(obj[key],result[key]) // 递归实现,重点也在这里
} else {
result[key] = obj[key] // 如果不是的可以直接相等
}
}
return result
}
var obj = {a: '1-1', b: [1,1]}
var obj1 = deepCopy(obj, {})
obj1.a = '2-1'
obj1.b = [2,2]
console.log(obj, obj1)
看结果:
看来这次改动复制得到的新对象,对原来的对象没有影响了,深复制成功!
当然,如果数据中不含有以下类型,也可以使用JSON.parse(JSON.stringify())进行深复制。
a.时间对象: Date() 被处理后会变成字符串
b.RegExp、Error对象 被处理成空对象
c.undefined 被处理丢掉
d.NaN、Infinity、-Infinity 被处理成null
e.构造函数 丢掉构造函数,以“Object”代替
f.有循环引用的数据
最后总结一下,赋值,浅拷贝和深拷贝的区别:
是否指向原对象 | 修改基础数据类型是否影响原对象 | 修改引用类型是否影响原对象 | |
赋值(=) | 是 | 是 | 是 |
浅拷贝 | 否 | 否 | 是 |
深拷贝 | 否 | 否 | 否 |
当然看其他博客中也介绍了jquery的$.extend() http://www.runoob.com/jquery/jquery-ref-misc.html方法,有兴趣的同学可以自己研究一下,毕竟封装起来的东西,通用性更广。