聊聊js中的值类型与引用类型

在日常的程序开发中,使用开发语言的特性是不可避免了,这里就简单的说明下JS中的值类型与引用类型,以及其中一些不为明知的特点。

1.首先我们来聊聊值类型

值类型其实很好理解:

1
2
3
4
5
6
7

var a = 10;
var b = a;
b = 11;

console.log(a); // =>10
console.log(b); // =>11

这里我们看到,我们首先定义了a,然后再将a赋值给b,接着改变b的值,最后输出a和b,得到两个完全不同的值(10和11)。像这样的情况,a,b 两者都是在系统的内存空间中开辟的属于自己的空间,那么他们不会受到彼此的影响,这就是我们所成为的值类型数据结构。

在JS中,字符串,布尔类型,数值,这样的基础数据类型,都是属于值类型。

1
2
3
4
5
6
7

var a = "sss";
var b = a;
b = "aaa";

console.log(a); // => sss
console.log(b); // => bbb

2.引用类型

引用类型就稍微需要我们理解一下了,我们用JS中的数组来说:

1
2
3
4
5
6
7

var a = [1,2,3];
var b = a;
b.push(4);

console.log(a); // => 1,2,3,4
console.log(b); // => 1,2,3,4

很不可思议的,我明明就没有对a数组进行操作,但两者的值却都发生了变化。

这个就是引用类型的特点,a数组在系统内存空间中开辟了一块空间,并将自己的[1,2,3]存入内存空间中去,然后

var b
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
2
3
4
5
6
7
8
9
10
11
12

var a = {
"name":"sa",
"age":25
}

var b = a;

b.name = "ca";

console.log(a.name); // =>ca
console.log(b.name); // =>ca

结果如我们所料,这样复制肯定不行,而在对象方法中,并没有像数组中concat这样能返回新数组的方法,那么我们就只能靠我们手动的方式,来进行对象复制了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

function copy(obj){
var _new = {};
for ( var attr in obj) {
_new[attr] = obj[attr];
}
return _new;
}

var a = {
"name":"sa",
"age":25
}

var b = copy(a);

b.name = "ca";

console.log(a.name); // =>sa
console.log(b.name); // =>ca

借助上面的函数,似乎我们完成了对对象的正确复制。但是,我们这里的对象属性都是很值类型,如果是引用类型,那么这招肯定是没用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function copy(obj){
var _new = {};
for ( var attr in obj) {
_new[attr] = obj[attr];
}
return _new;
}

var a = {
"name":"sa",
"age":25,
"work":{
address:"changsha"
}
}

var b = copy(a);

b.work.address = "beijing";

console.log(a.work.address ); // =>beijing
console.log(b.work.address ); // =>beijing

结果就是两个都是beijing。当然,我们可以改进上面的copy函数,让其在赋值时,判断下是不是引用类型,然后采用引用类型的方式来进行赋值,然而这看起来也太麻烦了,就没有稍微简单一点的安全复制对象的方式了吗。

当然有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = {
"name":"sa",
"age":25,
"work":{
address:"changsha"
}
}

var b = JSON.stringify(a);
b = JSON.parse(b);

b.work.address = "beijing";

console.log(a.work.address ); // =>changsha
console.log(b.work.address ); // =>beijing

借用我们的大JSON中的方法就行啦,先将obj=>sting,然后将string=>obj。这个是一个比较万金油的方法,很省心。

4.最后

最后这里聊聊我最近遇到的一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function Person(name){
this.name = name;
}

function ChangeName(obj){
obj.name = "Kobe";
obj = new Object();
obj.name = "James";
}

var person = new Person("Curry");
ChangeName(person);
console.log(person.name); // => "Kobe"

这是在小红书之中出现的一个例子。根据我们的js常识,我们会认为这里的输出应该是”James”,为啥呢,我们知道person是一个引用,当调用ChangeName这个函数时作为参数传进去,你改变了obj.name的值,外部的person.name其实和obj.name其实是同属于一个内存地址的,所以person.name也会跟着发生改变,也就是变成了”Kobe”,在这里我们都还能理解,之后obj = new Object(),新的obj.name是”James”,那么我们的person.name是不是要换成新的James呢?

这里我们可以回到之前的引用问题这么理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function Person(name){
this.name = name;
}

function ChangeName(obj){
var obj = obj;
obj.name = "Kobe";
obj = new Object();
obj.name = "James";
}

var person = new Person("Curry");
ChangeName(person);
console.log(person.name); // => "Kobe"

我只是在函数中加入了一行代码:var obj = obj;

引用中的obj作为参数传了进来,然而函数却使用了类似局部变量的方式保存了这个引用,所以当你改变函数中的obj.name时,确实操作到了参数中的obj的name地址(也就是person.name),可是,后面obj = new Object(),这样的操作,就像我们之前谈数组的引用一样,系统为obj开辟一块新的地址空间来储存数据,此时的这个obj就不再指向前面引用中的obj了(也就是person),而这个可怜的新obj,由于是在函数中声明的内部变量,在函数运行结束时,就被无情的消灭了,唯一对person引用的操作,只有obj.name = “Kobe”,这一句话,所以最后的输出,也就自然是Kobe了。