在日常的程序开发中,使用开发语言的特性是不可避免了,这里就简单的说明下JS中的值类型与引用类型,以及其中一些不为明知的特点。
1.首先我们来聊聊值类型
值类型其实很好理解:
1 |
|
这里我们看到,我们首先定义了a,然后再将a赋值给b,接着改变b的值,最后输出a和b,得到两个完全不同的值(10和11)。像这样的情况,a,b 两者都是在系统的内存空间中开辟的属于自己的空间,那么他们不会受到彼此的影响,这就是我们所成为的值类型数据结构。
在JS中,字符串,布尔类型,数值,这样的基础数据类型,都是属于值类型。
1 |
|
2.引用类型
引用类型就稍微需要我们理解一下了,我们用JS中的数组来说:
1 |
|
很不可思议的,我明明就没有对a数组进行操作,但两者的值却都发生了变化。
这个就是引用类型的特点,a数组在系统内存空间中开辟了一块空间,并将自己的[1,2,3]存入内存空间中去,然后 1
2
3
4
5
6
7
8
9
10
11
我们在看看下面的情况:
```javascript
var a = [1,2,3];
var b = a;
b = [1,2,3,4];
console.log(a); // => 1,2,3
console.log(b); // => 1,2,3,4
刚刚不是说好了,a和b两个都是共用了一个引用吗,为啥这里结果会不同呢?我们看两者唯一的不同,前者是使用了push这个函数来操作引用本身,而后者是使用了”=”来为b赋上了一个新的数组。这里的关键就是这个新的数组,新的数组就是在内存空间中开辟了新的空间,既然b已经有了属于自己的空间,为啥还要和a共用一块空间呢?于是,b就建立的新的引用,也就是指向新数组[1,2,3,4]的引用,此时,无论我们怎么改变b,a都不会受到任何影响了。
在JS中,所有的对象类型,函数类型,数组类型,都是引用类型。
3.关于引用对象的复制问题
那么我们需要复制引用类型的值的时候改咋办呢,我希望被复制的数组或对象不要受到影响。
关于数组的复制方式我在前面的一篇文章中已经有说过了,js如何正确复制一个数组,这里我就说说Object类型的数据该如何复制。
1 |
|
结果如我们所料,这样复制肯定不行,而在对象方法中,并没有像数组中concat这样能返回新数组的方法,那么我们就只能靠我们手动的方式,来进行对象复制了。
1 |
|
借助上面的函数,似乎我们完成了对对象的正确复制。但是,我们这里的对象属性都是很值类型,如果是引用类型,那么这招肯定是没用的。
1 | function copy(obj){ |
结果就是两个都是beijing。当然,我们可以改进上面的copy函数,让其在赋值时,判断下是不是引用类型,然后采用引用类型的方式来进行赋值,然而这看起来也太麻烦了,就没有稍微简单一点的安全复制对象的方式了吗。
当然有:
1 | var a = { |
借用我们的大JSON中的方法就行啦,先将obj=>sting,然后将string=>obj。这个是一个比较万金油的方法,很省心。
4.最后
最后这里聊聊我最近遇到的一个问题:
1 |
|
这是在小红书之中出现的一个例子。根据我们的js常识,我们会认为这里的输出应该是”James”,为啥呢,我们知道person是一个引用,当调用ChangeName这个函数时作为参数传进去,你改变了obj.name的值,外部的person.name其实和obj.name其实是同属于一个内存地址的,所以person.name也会跟着发生改变,也就是变成了”Kobe”,在这里我们都还能理解,之后obj = new Object(),新的obj.name是”James”,那么我们的person.name是不是要换成新的James呢?
这里我们可以回到之前的引用问题这么理解:
1 |
|
我只是在函数中加入了一行代码:var obj = obj;
引用中的obj作为参数传了进来,然而函数却使用了类似局部变量的方式保存了这个引用,所以当你改变函数中的obj.name时,确实操作到了参数中的obj的name地址(也就是person.name),可是,后面obj = new Object(),这样的操作,就像我们之前谈数组的引用一样,系统为obj开辟一块新的地址空间来储存数据,此时的这个obj就不再指向前面引用中的obj了(也就是person),而这个可怜的新obj,由于是在函数中声明的内部变量,在函数运行结束时,就被无情的消灭了,唯一对person引用的操作,只有obj.name = “Kobe”,这一句话,所以最后的输出,也就自然是Kobe了。