数据类型 | 深刻理解所有函数的参数都是按值传递的

这部分有一句很重要但是比较难理解的话:

ECMAScript 中所有函数的参数都是按值传递的

一看很懵,红宝书紧接着就做出了解释:

这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样

如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样

大概理解了,但是还是云里雾里,那么我们首先要清楚,按值传递和按引用传递参数会造成什么样的结果?

按值传递参数:值会被复制到一个局部变量(可以理解为arguments的一个槽位【补充:agruments是一个类数组对象,包含调用函数时传入的所有参数】)

按引用传递参数:在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部(在ECMAScript中显得不科学)

  • 看一下这个例子:
1
2
3
4
5
6
7
8
9
function addTen(num) {
num += 10;
return num;
};

let count = 10;
let res = addTen(count);
console.log(count); // 10 没有变化
console.log(res); // 20

原始值的例子很容易理解,num是一个局部变量,在调用时,count作为参数传入,被复制到num一边在addTen内部使用

在函数内部,参数num的值被加上了10,但这不会影响函数外部的原始变量count,参数num 和变量count 互不干扰,如果num 是按引用传递的,那么count 的值也会被修改为20

  • 再看一下一个引用数据类型的例子:(比较不容易理解)
1
2
3
4
5
6
7
8
9
10
function setName(obj) {
obj.name = 'Jenny'; // 修改传入obj的name属性的值
};

let person = {
name: 'Katrina',
};

setName(person); // 调用函数
console.log(person); // {name: 'Jenny'}

这次创建了一个对象并保存在person中,然后这个对象被传给setName方法,并且复制给obj

在函数内部,objperson是指向同一个对象的,这样就会导致,即使对象是按值传进函数的,obj也会通过引用访问对象,这样的话,当函数内部修改了objname属性的值的时候,函数外部的对象也会反映这个变化,因为,obj指向的对象保存在全局作用域的堆内存上

上面的过程会给人一种错觉:你在函数局部作用域中修改对象而导致反映到全局,不就意味着参数是按引用传递的吗?(一开始我也这样以为的!!!)

那么请看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function setName(obj) {
obj.name = 'Jenny';
obj = {
name: 'Jack',
};
};

let person = {
name: 'Katrina',
};

setName(person); // 调用函数
console.log(person); // {name: 'Jenny'}

这个例子比上面的例子就多了一行,也就是在函数内部将obj重新定义为了一个有着不同name的新对象

分析整个过程:

  • 创建了一个对象并保存在person中,然后这个对象被传给setName方法,并且复制给obj

  • 在函数内部内部修改了objname属性的值,并且又作妖地把obj设置为name'Jack'的一个新对象

  • 但是,当我们访问person.name的时候,打印的结果是Jenny而不是Jack

  • 这表明函数中参数的值改变之后,原始的引用仍然没变,当obj 在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了