Javascript 函数参数真的是按值传递吗?

174 阅读2分钟

Javascript 高级程序设计书中说,ESMAScript 中所有的函数都是按值传递的,但是真的是这样吗?

大家应该都知道 js 中有按值传递按引用传递两种。

按值传递就是值的复制;按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。

下面将会从参数是基础类型和引用类型两种来展开讲解。

基础类型变量

基础类型就是像 10(Number),'1'(String),true(Boolean) 等这种几类型。

下面是一个数字+10的例子,可以看到定义 count 初值为20,调用 add 方法,传参count,此时将 count 的值复制给 num,修改 num 并不会改变 count,所以这确实是按值传递没问题。

function add(num) {
    num += 10;
    return num;
};
let count = 20;
let result = add(count);
console.log(count); // 20
console.log(result); // 30

执行完 result 输出为30,count 没有被改变,输出还是 20。试想一下,如果函数传递的是引用类型,那么 count 应该也变成 30。

引用类型变量

引用类型就是像 Object 这种类型

下边是一个修改对象属性的例子,函数的参数是一个对象,函数执行完后,输出的 person 也被添加了 name 属性。此时就会有个疑问,不是按值传递吗,怎么函数外的对象也被修改了呢?

function setName(obj) {
    obj.name = 'a';
};
let person = {};
setName(person);
console.log(person); // { name: 'a' }

这里就要引入第三种传递类型按共享传递 ,如果说按引用传递是传递对象的引用,而按共享传递就是传递对象的引用的副本

所以 setName 函数内部的 obj 是 person 对象的引用的副本,指向的是同一块内存地址,修改 obj.name 就相当于修改了引用对象的属性。

再看下面这个例子:

function setName(obj) {
    obj.name = 'a';
    obj = {};
    obj.name = 'b';
    console.log(obj); // { name: 'b' }
};
let person = {};
setName(person);
console.log(person); // { name: 'a' }

在 setName 函数中加了对 obj 对象赋值{}后修改 name 为 b,此时函数内的 obj.name修改成了 b,但是函数外部的 person.name 还是 a,试想一下,如果是按引用访问,person.name 也应该变成 b。

这里又有疑问了,为什么和上边不一样呢,函数外的person 怎么没一起被改变呢?

我们上边说的对象是按共享传递,在函数内给 obj 赋值{}的时候,obj 指向了函数内局部的一块新的内存地址,这块内存在函数结束后就被销毁了,所以不会影响函数外的 person 对象。