javascript函数参数如何传递_怎样理解值传递和引用传递?

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

JavaScript 中所有函数参数都是值传递,没有真正的引用传递。 这句话不是绕口令,而是理解 JS 参数行为的关键前提。很多人看到 obj.name = 'Jerry' 能改外部对象,就以为是“引用传递”,其实只是传了一个地址的副本——它既不是纯值,也不是真引用,而是一种“传引用的副本”(pass by sharing)。

为什么基本类型修改不影响外部变量?

因为数字、字符串、布尔值等原始类型存储在栈中,传参时直接复制值本身:

function changeNum(n) {
  n = 100;
}
let x = 42;
changeNum(x);
console.log(x); // 42,没变
  • nx 的独立副本,改 n 就像改一张照片的打印件,原底片不受影响
  • 哪怕你在函数里重新赋值、加减、拼接,都只作用于这个局部拷贝
  • 这种行为在 numberstringbooleannullundefinedsymbol 上完全一致

为什么对象/数组能被“修改成功”?

因为传的是堆内存中对象地址的副本,形参和实参指向同一块内存区域:

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 变量仍指着旧地址
  • 这就像你和朋友共用一个云文档链接,你编辑内容大家都能看到;但如果你把链接改成另一个文档,朋友打开的还是原来的

常见踩坑场景与应对建议

实际开发中,最容易栽跟头的地方不是“能不能改”,而是“什么时候意外改了”:

  • Array.prototype.push().pop().splice() 等方法操作传入的数组,会直接污染原始数组——除非你明确想这样,否则应先 [...arr]arr.slice() 浅拷贝
  • 用解构赋值 { name } = obj 拿出属性后修改,不会影响 obj;但若解构后又执行 obj.name = 'xxx',就又回到共享地址的老问题
  • 函数返回新对象(如 return { ...obj, age: newAge })比原地修改更安全,尤其在 React/Vue 的响应式逻辑中,避免触发不必要的更新
  • 调试时别只看 console.log 的输出——用 === 判断两个变量是否指向同一对象,比“看起来一样”更可靠

真正容易被忽略的,不是“对象能被改”,而是“改的是谁的副本”。只要记住:JS 里没有魔法,只有栈里的地址拷贝 + 堆里的真实数据。传什么,就只能动什么;想不动原数据,就得自己做拷贝。