到底什么是“值传递”与“引用传递”

前言

今天在逛掘金的时候无意中发现一道坑了我许久的题,原题如下,第一眼看过去,我擦,这道题还是比较坑多的呀,第一个坑是函数声明会提前,第二个坑是函数内部直接声明变量(不带var),会在全局中声明这个变量,执行foo函数后,a就就污染了全局变量,这样最后输出1,心里还在暗自高兴,以为成功越过这个坑,结果。。。啪啪啪打脸。。。

1
2
3
4
5
6
var a=100;
function foo(a){
a=1;
}
foo(a);
console.log(a) // 100

分析

这道题想表达的意思和全局变量污染没有一点关系,重点是JavaScript中的值传递和引用传递,因为执行foo时传入一个a形参,也就是相当于下面,在函数foo中新建了一个函数内部变量,压根不会影响到外面的a,所以很简单,最终输出100。

1
2
3
4
5
6
7
var a=100;
function foo(a){
var a = 100;
a=1;
}
foo(a);
console.log(a) // 100

你以为事情就这么简单吗,并没有,请签收下面两道大坑题

基础概念

  • 5种基本数据类型:Undefined、Null、Boolean、Number、String
  • 复杂数据类型:object

基本类型数据

  1. 基本数据类型值是指简单的数据段,五种基本类型都是按值访问的(可以操作保存在变量中的实际值);

  2. 基本类型的值在内存中占据固定大小的空间,被保存在栈内存中。(从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本);

  3. 不能给基本类型的值添加属性。

引用类型数据:object(还有array、function)

  1. 引用类型值是指那些可以由多个值构成的对象。js不允许直接访问内存中的位置,也就是不能直接访问操作对象的内存空间,在操作对象时,实际上是在操作对象的引用而不是实际的对象;

  2. 引用类型的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终指向同一个对象。

  3. 对于引用类型的值,可以为其添加属性和方法,也可以改变和删除其属性和方法。

引出题

JavaScript中对象的传递与基本类型不同,它是按引用传递的。我对按引用传递的理解是——按引用享传递时,我们应该把形参看做由两部分组成,一部分是形参本身、一部分是形参属性所指向的地址。形参本身是按值传递的,但形参属性所指向的地址却和实参属性地址一样,可以看作按引用传递。

talk is cheap:

1
2
3
4
5
6
7
8
9
10
11
12
var a = {
x: 1,
y: 2
};
function foo(obj) {
obj = {
x: 3,
y: 4
};
}
foo(a);
console.log(a); // { x: 1, y: 2 }

最后输出的结果a仍然是{x:1,y:2}。这是因为形参本身是按值传递的,修改形参本身不会造成实参的修改,如果我们把上面的代码改成如下这样子:

1
2
3
4
5
6
7
8
9
10
var a = {
x: 1,
y: 2
};
function foo(obj) {
obj.x = 3;
obj.y = 4;
}
foo(a);
console.log(a); // { x: 3, y: 4 }

最后就会很神奇的发现a被修改为了{x:3,y:4},这是因为形参属性所指向的地址与实参所指向的地址一样,修改的形参的属性就同时修改了实参的属性。

总结

这是引用类型的值传递存在较多的不确定性,当我们想要使用这个对象但是又不想污染该对象是,我们时时刻刻要注意,当然你可以通过对象的深浅拷贝,来克隆一个“相同”的变量,这样就能放心大胆地来使用对象啦~