# 前言
在正式讨论函数参数传递之前,我们先来了解下以下几个基础的概念,就是基本类型和引用类型以及变量值的复制。
# 基本类型
ECMAScript 变量可能包含两种不同类型的值:基本类型值和引用类型值。基本类型指的是简单的数据段,引用类型值指那些可能由多个值构成的对象。有6种基本类型:Undefined
、Null
、Number
、String
、Boolean
和 Symbol
。这6种基本类型都是按值访问的,因为可以操作保存在变量中的实际的值。
# 变量值的复制
# 基本类型的复制
复制基本类型的值,会在内存中重新分配一个内存给新变量,复制前后的值互不影响。
var num1 = 5;
var num2 = num1;
2
# 引用类型的复制
当复制引用类型时,同样也会将储存在变量对象中的值复制一份到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个 指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此改变其中的一个变量,就会影响另一个变量,看个例子:
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'foo';
console.log(obj2.name); // foo
2
3
4
obj1 保存了一个对象新实例,这个值被复制到 obj2 中,这两个变量引用都指向同一个对象,所以其中一个对象的修改会影响到另一个变量的值。
运算符=
操作,记住关键的一句话是:
运算符
=
就是创建或修改变量在内存中的值初始化变量时即为创建,重新赋值就是修改
看另一个例子:
var obj1 = { b: 1 }; // 创建变量 obj1 指向 对象 { b: 1 }
var obj2 = obj1; // 创建变量 obj2 指向 对象 { b: 1 }
obj1 = { c: 2 }; // 重新赋值,修改 obj1 的指向为 { c: 2 }
console.log(obj2); // obj2 的指向没变,还是指向对象 { b: 1 }
2
3
4
obj1通过重新赋值改变了指向,用图来表示就是下图红色箭头所示:
# 参数传递
了解了变量值复制的概念,我们来看下针对不同的参数类型,在函数中是怎么传递的。
# 基本类型
基本类型的传递如同基本类型变量的复制一样。
举个例子:
var value = 1;
function foo(v) {
v = 2;
console.log(v); // 2
}
foo(value);
console.log(value); // 1
2
3
4
5
6
7
这个例子不难理解,当传递 value 到函数 foo 中,相当于重新拷贝了一份 value ,假设拷贝后的副本叫 _value, 函数修改的 都是 _value 的值,不影响原来的 value 值。
# 引用类型
当传递的参数是引用类型时,其实跟引用类型的复制如出一撤。这时传递的是引用类型的引用的副本。 举个例子:
var obj = {
value: 1
}
function foo(o) {
o.value = 2;
console.log(o.value); // 2
}
foo(obj);
console.log(obj.value); // 2
2
3
4
5
6
7
8
9
10
上面这段代码等价于:
var obj = {
value: 1
}
function foo() {
var o = obj
o.value = 2;
console.log(o.value); // 2
}
foo(obj);
console.log(obj.value); // 2
2
3
4
5
6
7
8
9
10
11
o 变量复制了 obj 对象,指向同一个对象{ value: 1 }
, 下一行代码修改了对象的值{ value: 2 }
,所以打印出来的 value 是同一个值。
我们再看一个例子:
var obj = {
value: 1
}
function foo(o) {
o = { value: 3 };
console.log(o.value); // 3
}
foo(obj);
console.log(obj.value); // 1
2
3
4
5
6
7
8
9
10
上面这段代码等价于
var obj = {
value: 1
}
function foo() {
var o = obj;
o = { value: 3 };
console.log(o.value); // 3
}
foo(obj);
console.log(obj.value); // 1
2
3
4
5
6
7
8
9
10
11
在函数内部,传递 obj 相当于赋值 obj 给一个新创建的内部变量 o,重新赋值 o 为{ value: 3 }
, 这时就修改了 o 的引用,不再指向{ value: 1 }
,所以内部修改 o.value 并不会影响到外部的值。
类比来说
- 变量名与变量值的关系好比快捷方式与真实文件的关系
- 值类型类比为文件,引用类型类比为文件夹
var obj = { // 1、创建一个文件夹 "{value: 1}", 2、创建一个快捷方式 obj
value: 1
}
function foo(o) { // 4、形参 o: 创建 o 的快捷方式,o 指向快捷方式 obj 本身(快捷方式的快捷方式, 即为引用的副本)
o = { value: 3 }; // 5、修改 o 快捷方式的指向,指向文件夹 { value: 3 }
console.log(o.value);
}
foo(obj); // 3、传递实参 obj 快捷方式
console.log(obj.value);
2
3
4
5
6
7
8
9
10
# 总结
综上所述,传递引用类型时,其实相当于把引用的值传递到函数内部,创建形参即复制引用,可以看做是直接传递引用副本。 如果重新赋值形参,则重新修改了这个引用,如果没有重新赋值,还是引用同一个对象,这样就会影响其他共同指向这个对象的值。
这种传递引用副本的方式专业的术语叫做共享传递。
所以在《JavaScript高级程序设计》第三版 4.1.3,讲到传递参数:
ECMAScript中所有函数的参数都是按值传递的。
参数如果是基本类型是按值传递,如果是引用类型按共享传递。 但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递了。