JavaScript 参数传递机制详解:按值传递与按共享传递
本文深入探讨 JavaScript 的函数参数传递机制。虽然 ECMAScript 规定所有参数均为按值传递,但针对引用类型,实际表现为按共享传递(Call by Sharing)。文章通过基本类型与对象类型的代码示例,解析变量复制、属性修改与重新赋值的区别,并补充 ES6 默认参数的惰性求值特性。
近期在研究 lambda 演算中的 η-变换在 JavaScript 中的应用时,偶然发现 StackOverflow 上一个关于 JavaScript 求值策略的有趣讨论:函数参数究竟是按值传递还是按引用传递?该问题的回答颇具代表性。
核心结论概述
function changeStuff(a, b, c) {
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num); // 10
console.log(obj1.item); // changed
console.log(obj2.item); // unchanged
若假设 JavaScript 函数参数采用按值传递,则在函数 changeStuff 内部修改 b.item 的值时,不应影响外部 obj1 对象的值。
若假设 JavaScript 函数参数采用按引用传递,则函数 changeStuff 内部的修改应影响外部所有变量定义,例如 num 变为 100、obj2.item 变为 changed。然而实际情况并非如此。
因此,不能简单断言 JavaScript 参数传递严格属于按值传递或按引用传递。总体而言,JavaScript 函数的参数传递机制可概括为按值传递。此外,JavaScript 还采用了一种称为按共享传递(Call by Sharing)的策略,具体表现取决于参数的类型。
当参数为基本类型时,遵循按值传递规则;
当参数为引用类型时,遵循按共享传递规则。
参数传递机制解析
根据《JavaScript 高级程序设计》所述,ECMAScript 中所有函数参数均按值传递。这意味着将函数外部的值复制到函数内部的参数中,其过程类似于将值从一个变量复制到另一个变量。基本类型值的传递等同于基本类型变量的复制,而引用类型值的传递则等同于引用类型变量的复制。
红宝书指出所有函数参数均按值传递,这是否准确?让我们结合上述示例进行分析:
按值传递机制
在 JavaScript 中,基本类型作为参数时采用按值传递(Call by Value)策略:
function foo(a) {
a = a * 10;
}
var num = 10;
foo(num);
console.log(num); // 10 没有变化
从代码可见,函数内部对参数的修改并未影响外部变量,证实了按值传递的特性。
按共享传递机制
在 JavaScript 中,对象作为参数时采用按共享传递(Call by Sharing)策略:
修改参数的属性会反映到外部对象上。
对参数进行重新赋值则不会影响外部对象。
根据上述示例,函数内部修改了参数 b 的属性 item,导致外部对象 obj1 的属性 item 也随之改变。
function bar(b) {
b.item = "changed";
console.log(b === obj1) // true
}
var obj1 = {item: "unchanged"};
bar(obj1);
console.log(obj1.item); // changed 修改参数的属性将会影响到外部对象
从 b === obj1 打印结果为 true 可以看出,函数内部修改参数属性并未改变参数本身的引用。b 和 obj1 共享同一个对象地址,因此修改参数属性会影响外部对象。
然而,若将参数 c 重新赋值为一个新对象,则不会影响外部对象。
function baz(c) {
c = {item: "changed"};
console.log(c === obj2) // false
}
var obj2 = {item: "unchanged"};
baz(obj2);
console.log(obj2.item); // unchanged 重新赋值将不会影响到外部对象
当参数 c 被重新赋值为一个新对象时,c 将绑定到新的对象地址,此时 c === obj2 打印结果为 false,表明它们不再共享同一对象地址。由于各自拥有独立的对象地址,因此重新赋值不会影响外部对象。
总结
可以说,按共享传递是按值传递的一种特例,其传递的是引用地址的副本。因此,红宝书的说法也是正确的。
可以将 ECMAScript 函数的参数视为局部变量。-- 《JavaScript 高级程序设计》
延伸:惰性求值
前文已明确所有函数参数均按值传递,且 JavaScript 中参数需先求值再作为实参传入函数。但在 ES6 中存在一个特例。
参数默认值并非传值,而是每次调用时重新计算默认值表达式的结果,即参数默认值采用惰性求值。-- 《ECMAScript 6 入门》
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
在上述代码中,参数 p 的默认值为 x + 1。每次调用函数 foo 时,都会重新计算 x + 1,而非默认 p 等于 100。
以上即为关于 JavaScript 求值策略的详细讨论,更多 JavaScript 求值策略相关资料请关注本站其他文章!
JS合并多个数组的三种方法:concat、spread与flat
2026-11-05 17:39:45
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
相关标签:
