主题
值传递与引用传递
在 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 = {} // 不可以