JavaScript函数参数均为值传递,即基本类型传值副本、对象传地址副本;修改对象属性会影响外部,但重赋值不会;应通过浅拷贝或返回新对象避免意外修改。

JavaScript 中所有函数参数都是值传递,没有真正的引用传递。 这句话不是绕口令,而是理解 JS 参数行为的关键前提。很多人看到 obj.name = 'Jerry' 能改外部对象,就以为是“引用传递”,其实只是传了一个地址的副本——它既不是纯值,也不是真引用,而是一种“传引用的副本”(pass by sharing)。
为什么基本类型修改不影响外部变量?
因为数字、字符串、布尔值等原始类型存储在栈中,传参时直接复制值本身:
function changeNum(n) {
n = 100;
}
let x = 42;
changeNum(x);
console.log(x); // 42,没变
-
n是x的独立副本,改n就像改一张照片的打印件,原底片不受影响 - 哪怕你在函数里重新赋值、加减、拼接,都只作用于这个局部拷贝
- 这种行为在
number、string、boolean、null、undefined、symbol上完全一致
为什么对象/数组能被“修改成功”?
因为传的是堆内存中对象地址的副本,形参和实参指向同一块内存区域:
function mutateObj(o) {
o.age = 30; // ✅ 修改属性:生效
o = { name: 'Bob' }; // ❌ 重赋值:断开连接,不影响外部
}
let person = { name: 'Alice' };
mutateObj(person);
console.log(person.age); // 30
console.log(person.name); // 'Alice'(没变成 'Bob')
- 第一行
o.age = 30是通过地址找到原对象并写入,所以外部可见 - 第二行
o = {...}是让局部变量o指向新对象,原person变量仍指着旧地址 - 这就像你和朋友共用一个云文档链接,你编辑内容大家都能看到;但如果你把链接改成另一个文档,朋友打开的还是原来的
常见踩坑场景与应对建议
实际开发中,最容易栽跟头的地方不是“能不能改”,而是“什么时候意外改了”:
立即学习“Java免费学习笔记(深入)”;
- 用
Array.prototype.push()、.pop()、.splice()等方法操作传入的数组,会直接污染原始数组——除非你明确想这样,否则应先[...arr]或arr.slice()浅拷贝 - 用解构赋值
{ name } = obj拿出属性后修改,不会影响obj;但若解构后又执行obj.name = 'xxx',就又回到共享地址的老问题 - 函数返回新对象(如
return { ...obj, age: newAge })比原地修改更安全,尤其在 React/Vue 的响应式逻辑中,避免触发不必要的更新 - 调试时别只看 console.log 的输出——用
===判断两个变量是否指向同一对象,比“看起来一样”更可靠
真正容易被忽略的,不是“对象能被改”,而是“改的是谁的副本”。只要记住:JS 里没有魔法,只有栈里的地址拷贝 + 堆里的真实数据。传什么,就只能动什么;想不动原数据,就得自己做拷贝。











