一、核心结论
JavaScript 中所有函数参数都是按值传递的。这个"值"指的是存储在栈内存中的值,即:
- 对于基本类型,传递的是值的拷贝
- 对于引用类型,传递的是引用地址值的拷贝
二、基本类型的参数传递
function changeValue(num) {
num = 200;
return num;
}
let x = 100;
changeValue(x);
console.log(x); // 仍然是 100
当传递基本类型时:
- 在栈内存中创建一个新的值拷贝
- 函数内的形参获得这个值的副本
- 对形参的任何修改都不会影响原始值
三、引用类型的参数传递
function changeObject(obj) {
obj.name = "new name"; // 会影响原始对象
obj = { name: "another" }; // 不会影响原始对象
}
let person = { name: "original" };
changeObject(person);
console.log(person.name); // "new name"
当传递引用类型时:
- 栈内存中复制的是引用地址(指针)
- 形参和实参指向堆内存中的同一个对象
- 通过引用修改对象的属性会影响原始对象
- 给形参赋值新对象不会影响实参的引用
四、关键理解点
-
统一的传值规则
- 无论是什么类型的参数,栈内存中都会创建一个新的值
- 区别仅在于基本类型传递的是数据值,引用类型传递的是引用地址值
-
引用类型的特殊性
- 虽然引用地址是按值传递的,但因为指向同一个对象,所以修改对象属性会影响原始对象
- 只有给形参赋值新对象时,才会改变形参的引用,而不影响实参
-
从内存角度理解
- 栈内存:存储基本类型值和引用地址
- 堆内存:存储引用类型的对象内容
- 参数传递时只复制栈内存中的内容
五、常见误区
-
"引用传递"这个说法的误解
- JavaScript 中并不存在真正的引用传递
- 传递的总是值,只不过对于对象来说,这个值是引用地址
-
混淆对象属性的修改和引用的修改
- 修改对象属性:会影响原始对象
- 修改引用指向:不会影响原始对象
六、实际应用建议
-
函数设计
- 如果不希望修改原始对象,应在函数内创建对象的拷贝
- 明确函数是否会修改传入的对象,并在文档中说明
-
代码可维护性
- 理解参数传递机制有助于写出可预测的代码
- 避免在函数内部直接修改对象,除非这是明确的设计意图
-
性能考虑
- 对大对象进行值传递时,只传递引用地址而不是复制整个对象
- 这种机制使得 JavaScript 在处理大对象时仍然保持高效
七、总结
JavaScript 的参数传递机制是一致的:都是按值传递。理解这一点对于写出可靠的代码至关重要。当我们说对象是按引用传递时,实际上是引用地址的值传递,这种理解可以帮助我们更好地预测和控制函数的行为。