Skip to content

值传递与引用传递

在 JavaScript 里,函数参数传递的本质是按值传递。只不过当这个“值”本身是对象引用时,表现出来很像“引用传递”,因此很容易混淆。

先说结论

  • 原始类型传入函数时,复制的是值本身。
  • 对象传入函数时,复制的是“对象引用”这个值。
  • JavaScript 没有传统意义上的按引用传递。

原始类型的表现

js
let count = 1

function changeValue(value) {
  value = 2
}

changeValue(count)

console.log(count) // 1

函数内部修改的是参数副本,不会影响外部变量。

对象为什么看起来像按引用传递

js
const user = { name: 'Tom' }

function changeName(obj) {
  obj.name = 'Jerry'
}

changeName(user)

console.log(user.name) // Jerry

这里并不是把 user 变量本身传进去了,而是把它保存的那个引用值复制了一份。函数内外都通过这个引用访问同一个对象,所以修改属性会互相影响。

重新赋值为什么又不会影响外部

js
const user = { name: 'Tom' }

function reset(obj) {
  obj = { name: 'Jerry' }
}

reset(user)

console.log(user.name) // Tom

原因是:

  • 传入函数时,obj 拿到的是引用值的副本。
  • obj = {...} 只是让 obj 指向了新对象。
  • 外部的 user 仍然指向原来的对象。

可以这样理解

把变量想象成“格子”,对象想象成“堆里的实体”:

  • 原始类型:格子里直接放值。
  • 对象类型:格子里放的是对象地址。

函数调用时,会复制格子里的内容:

  • 复制原始值,彼此独立。
  • 复制对象地址,仍指向同一份对象。

常见示例

修改对象属性

js
const config = { dark: false }

function enableDarkMode(options) {
  options.dark = true
}

enableDarkMode(config)

console.log(config.dark) // true

交换为新对象

js
const config = { dark: false }

function replaceConfig(options) {
  options = { dark: true }
}

replaceConfig(config)

console.log(config.dark) // false

和浅拷贝的关系

对象赋值默认只复制引用,不复制内部内容。

js
const a = { nested: { count: 1 } }
const b = a

b.nested.count = 2

console.log(a.nested.count) // 2

如果希望得到相互独立的新对象,需要显式拷贝。

js
const a = { name: 'Tom', age: 18 }
const b = { ...a }

b.name = 'Jerry'

console.log(a.name) // Tom

但要注意,这只是浅拷贝:

js
const a = { nested: { count: 1 } }
const b = { ...a }

b.nested.count = 2

console.log(a.nested.count) // 2

开发中的判断方式

看到函数参数是对象时,可以先分两类想:

  • 修改对象属性:通常会影响外部。
  • 给参数重新赋值:通常不会影响外部变量本身。

常见误区

“对象是按引用传递”

这个说法不够准确。更准确的说法是:对象引用这个值是按值传递。

const 对象不能修改”

const 限制的是变量绑定不能重新赋值,不是对象内容完全不可变。

js
const user = { name: 'Tom' }
user.name = 'Jerry' // 可以
// user = {} // 不可以

基于 MIT 许可发布