前言
在 JavaScript 面试中,参数传递机制是一个常见考点。很多人对"对象作为参数传递"这一概念存在误解。本文将通过一道经典面试题,带你彻底理解 JavaScript 的参数传递机制。
题目代码
javascript
function test(person) {
person.age = 26
person = {
name: 'hzj',
age: 18
}
return person
}
const p1 = {
name: 'fyq',
age: 19
}
const p2 = test(p1)
console.log(p1) // 输出什么?
console.log(p2) // 输出什么?
答案解析
输出结果
javascript
console.log(p1) // { name: 'fyq', age: 26 }
console.log(p2) // { name: 'hzj', age: 18 }
详细解析
1. JavaScript 的参数传递机制
首先要明确:JavaScript 中所有函数参数都是按值传递的。这是一个关键点,很多人会误解为"对象按引用传递"。
- 基本类型:传递的是值的副本
- 引用类型:传递的是引用的副本(可以理解为指针的副本)
2. 代码执行过程分析
第一步:函数调用前
javascript
const p1 = {
name: 'fyq',
age: 19
}
此时内存中存在一个对象,p1
保存着指向这个对象的引用。
第二步:调用 test(p1)
javascript
function test(person) {
// 此时 person 和 p1 指向同一个对象
person.age = 26 // 修改了原始对象的属性
// ...
}
当调用 test(p1)
时,实际上是将 p1
保存的引用复制了一份传递给 person
参数。此时 person
和 p1
指向内存中的同一个对象。
执行 person.age = 26
时,通过这个引用修改了原始对象的 age
属性。因此,p1
指向的对象已经被改变。
第三步:重新赋值 person
javascript
function test(person) {
// ...
person = {
name: 'hzj',
age: 18
} // person 现在指向一个新对象
// ...
}
这里的关键点:person = {...}
并不是修改原始对象,而是将局部变量 person
重新赋值为一个新对象的引用。这切断了 person
与原始对象的关联,但不会影响 p1
仍然指向原始对象。
第四步:函数返回并赋值
javascript
return person // 返回新对象的引用
const p2 = test(p1) // p2 指向函数返回的新对象
函数返回了新对象的引用,p2
接收了这个引用,指向新创建的对象 {name: 'hzj', age: 18}
。
核心概念总结
值传递 vs 引用传递
- 值传递:传递的是变量的副本,修改参数不会影响原始变量
- 引用传递:传递的是变量的实际地址,修改参数会影响原始变量
- JavaScript 的情况:对于对象类型,传递的是引用的副本(可以称为"共享传递")
关键区别
操作类型 | 对原始对象的影响 | 示例 |
---|---|---|
修改对象属性 | ✅ 会影响原始对象 | person.age = 26 |
重新赋值参数 | ❌ 不会影响原始对象 | person = {...} |
实际应用场景
理解这一机制对于避免以下常见错误至关重要:
- 意外修改原始数据:在函数中修改传入对象的属性时,可能会意外改变原始数据
- 函数式编程:需要保持数据不可变性时,应该先创建副本再修改
- 状态管理:在 React 等框架中,直接修改状态对象会导致无法检测到状态变化
思考题
如果你希望函数内部的操作不影响原始对象,应该怎么做?
javascript
// 解决方案:创建对象的副本
function test(person) {
const newPerson = {...person} // 浅拷贝
newPerson.age = 26
// ... 其他操作
return newPerson
}
结语
JavaScript 的参数传递机制虽然简单,但理解其细微差别对于编写可靠的代码至关重要。记住:对象作为参数传递时,传递的是引用的副本,修改属性会影响原始对象,但重新赋值参数变量不会影响原始对象