【前端面试小册】JS-第15节:函数形参与实参

一、核心概念

1.1 定义

函数的参数都是按值传递的

  • 基本类型:复制值传递
  • 复杂类型:复制指针传递

类比理解:参数传递就像复印文件。基本类型就像复印文字内容(复制值),复杂类型就像复印文件地址(复制指针),你修改复印件不会影响原件,但通过地址找到的文件是同一个。

1.2 关键理解

JavaScript 中没有引用传递,只有按值传递。即使是对象,传递的也是对象引用的值(指针),而不是对象本身。

二、按值传递(基本类型)

2.1 基本示例

const name = '成都巴菲特';

function demo(myName) {
    myName = '巴菲特';
    console.log(myName);  // 巴菲特
}

demo(name);
console.log(name);  // 成都巴菲特(原值未改变)

2.2 原理分析

传递过程

graph TD
    A[全局变量 name = 成都巴菲特] --> B[调用 demo name]
    B --> C[复制 name 的值]
    C --> D[传递给形参 myName]
    D --> E[myName = 巴菲特]
    E --> F[只修改了 myName,不影响原值]
    F --> G[name 仍然是 成都巴菲特]

关键点

  • 调用函数 demo 时,把全局 name 的值会拷贝一份,然后传递给 myName
  • 所以函数内部修改 myName 的值是不会影响全局的 name 的值
  • myName 是函数内部作用域的变量

2.3 内存模型

// 全局作用域
name: '成都巴菲特'  // 内存地址 A

// 函数调用时
myName: '成都巴菲特'  // 内存地址 B(新创建,复制了值)

// 函数内部修改
myName: '巴菲特'  // 内存地址 B(修改了副本)

// 全局变量不变
name: '成都巴菲特'  // 内存地址 A(原值未变)

三、按值传递(复杂类型 - 复制指针)

3.1 基本示例

let obj = {
    name: '成都巴菲特'
};

function demo(myObj) {
    myObj.name = '巴菲特';
    console.log(myObj.name);  // '巴菲特'
}

demo(obj);
console.log(obj.name);  // '巴菲特'(原对象被修改)

3.2 原理分析

传递过程

graph TD
    A[全局变量 obj 指向对象A] --> B[调用 demo obj]
    B --> C[复制 obj 的指针值]
    C --> D[传递给形参 myObj]
    D --> E[myObj 和 obj 指向同一对象]
    E --> F[修改 myObj.name]
    F --> G[实际修改的是同一个对象]
    G --> H[obj.name 也被修改]

关键理解

  • 复杂类型传值的时候,是复制指针进行传递
  • 所以这个时候 myObjobj 都是指向堆内存的同一个对象
  • 所以 myObj.name = '巴菲特' 修改的就是 obj 指向的那个对象,所以 obj.name 值也修改

3.3 内存模型

// 全局作用域
obj: 0x001  →  [对象A: { name: '成都巴菲特' }]  // 堆内存

// 函数调用时
myObj: 0x001  →  [对象A: { name: '成都巴菲特' }]  // 复制了指针,指向同一对象

// 函数内部修改
myObj: 0x001  →  [对象A: { name: '巴菲特' }]  // 修改了对象属性

// 全局变量也受影响
obj: 0x001  →  [对象A: { name: '巴菲特' }]  // 因为是同一个对象

3.4 重新赋值的情况

let obj = {
    name: '成都巴菲特'
};

function demo(myObj) {
    myObj.name = '巴菲特';  // 修改对象属性
    myObj = {};             // 重新赋值,指向新对象
    myObj.name = '成都';    // 修改新对象的属性
    console.log(myObj.name);  // '成都'
}

demo(obj);
console.log(obj.name);  // '巴菲特'(原对象属性被修改,但对象本身未变)

详细分析

graph TD
    A[obj 指向对象A] --> B[myObj 也指向对象A]
    B --> C[修改 myObj.name = 巴菲特]
    C --> D[对象A的name被修改]
    D --> E[myObj = 新对象B]
    E --> F[myObj 现在指向对象B]
    F --> G[obj 仍然指向对象A]
    G --> H[修改 myObj.name = 成都]
    H --> I[只影响对象B,不影响对象A]

步骤说明

  1. myObj.name = '巴菲特'

    • myObjobj 还是指向堆中同一个对象(对象 A),所以 name 会被修改
  2. myObj = {}

    • 这一步会让 myObj 重新指向堆中的一个新对象(对象 B)
    • 这一步后 myObjobj 指向堆中的对象不一样了
    • myObj 是函数的局部变量,在函数执行完毕就会被销毁
  3. myObj.name = '成都'

    • 因为 myObj 重新指向了一个局部对象,所以修改的值不会影响 obj 指向的对象
  4. 结果

    • console.log(myObj.name) 输出对象 B 的 name成都
    • console.log(obj.name) 输出对象 A 的 name巴菲特

四、面试题

4.1 题目 1:未声明的变量

let name = '成都巴菲特';

function demo() {
    name = '巴菲特';  // 没有声明,直接赋值
    console.log(name);  // 巴菲特
}

demo();
console.log(name);  // 巴菲特(全局变量被修改)

分析

  • 这里是因为 name = xxx 这种形式,相当于定义的全局变量
  • 所以全局变量 name 被修改
  • 这块内容在函数作用域讲过,可以去复习

关键点

  • 函数内部未声明的变量赋值,会创建全局变量(非严格模式)
  • 严格模式下会报错:ReferenceError

4.2 题目 2:重新赋值对象

let obj = {
    name: '成都巴菲特'
};

function demo(myObj) {
    myObj.name = '巴菲特';    // 修改对象属性
    myObj = {};               // 重新赋值
    myObj.name = '成都';      // 修改新对象属性
    console.log(myObj.name);  // 成都
}

demo(obj);
console.log(obj.name);  // '巴菲特'

详细分析

// 步骤 1:myObj 和 obj 还是指向堆中同一个对象(对象 A),所以 name 会被修改
myObj.name = '巴菲特';

// 步骤 2:这一步会让 myObj 重新指向堆中的一个新对象(对象 B)
// 这一步后 myObj 和 obj 指向堆中的对象不一样了
// 而 myObj 是函数的局部变量,在函数执行完毕就会被销毁
myObj = {}

// 步骤 3:因为 myObj 重新指向了一个局部对象,所以修改的值不会影响 obj 指向的对象
myObj.name = '成都'

// 步骤 4:所以这里输出对象 B 的 name
console.log(myObj.name)  // 成都

// 步骤 5:输出对象 A 的 name
console.log(obj.name)    // 巴菲特

内存变化图

graph TD
    A[初始: obj → 对象A name=成都巴菲特] --> B[调用: myObj → 对象A]
    B --> C[修改: 对象A name=巴菲特]
    C --> D[重新赋值: myObj → 对象B]
    D --> E[修改: 对象B name=成都]
    E --> F[结果: obj → 对象A name=巴菲特]
    E --> G[结果: myObj → 对象B name=成都]

五、参数传递的本质

5.1 为什么是值传递而不是引用传递?

引用传递的特点(JavaScript 不支持):

  • 如果支持引用传递,形参和实参应该是同一个变量
  • 修改形参应该直接修改实参本身

值传递的特点(JavaScript 支持):

  • 形参是实参的副本
  • 基本类型:复制值
  • 复杂类型:复制指针值

5.2 证明是值传递

let obj = { name: 'A' };

function demo(myObj) {
    myObj = { name: 'B' };  // 重新赋值
    console.log(myObj.name);  // B
}

demo(obj);
console.log(obj.name);  // A(原对象未变)

分析

  • 如果是引用传递,myObj = { name: 'B' } 应该直接修改 obj
  • 但实际 obj 未变,说明是值传递(复制了指针值)

5.3 为什么对象会被修改?

let obj = { name: 'A' };

function demo(myObj) {
    myObj.name = 'B';  // 修改对象属性
    console.log(myObj.name);  // B
}

demo(obj);
console.log(obj.name);  // B(对象被修改)

分析

  • 虽然传递的是指针值的副本,但两个指针指向同一个对象
  • 修改对象属性时,通过指针找到的是同一个对象
  • 所以原对象会被修改

类比

  • 就像两个人各有一把钥匙(指针副本),但打开的是同一扇门(对象)
  • 其中一个人修改了门上的装饰(对象属性),另一个人看到的门也会变化

六、实际应用场景

6.1 函数参数保护

// ✅ 好的做法:不修改原对象
function processData(data) {
    // 创建副本,避免修改原对象
    const processed = { ...data };
    processed.status = 'processed';
    return processed;
}

// ❌ 不好的做法:直接修改原对象
function processData(data) {
    data.status = 'processed';  // 修改了原对象
    return data;
}

6.2 深拷贝 vs 浅拷贝

// 浅拷贝:只复制第一层
function shallowCopy(obj) {
    return { ...obj };
}

// 深拷贝:递归复制所有层级
function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}

// 使用示例
const original = {
    name: 'A',
    nested: { value: 1 }
};

const shallow = shallowCopy(original);
shallow.nested.value = 2;
console.log(original.nested.value);  // 2(被修改了)

const deep = deepCopy(original);
deep.nested.value = 3;
console.log(original.nested.value);  // 2(未被修改)

七、思考

函数传入复杂类型,复制指针而不是整个值,是为了性能考虑。

原因

  • 如果复制整个对象,对于大对象会消耗大量内存和时间
  • 复制指针只需要复制一个地址值(通常 8 字节),非常高效
  • 这是 JavaScript 设计上的权衡

八、面试要点总结

核心知识点

  1. 参数传递方式:JavaScript 中所有参数都是按值传递
  2. 基本类型:复制值传递,修改不影响原值
  3. 复杂类型:复制指针传递,修改对象属性会影响原对象,但重新赋值不会
  4. 性能考虑:复制指针而不是整个对象,提高性能

常见面试题

Q1: JavaScript 的参数传递是按值传递还是按引用传递?

答:按值传递。基本类型复制值,复杂类型复制指针值。JavaScript 没有真正的引用传递。

Q2: 为什么修改对象属性会影响原对象?

答:虽然传递的是指针值的副本,但两个指针指向同一个对象。修改对象属性时,通过指针找到的是同一个对象,所以原对象会被修改。

Q3: 如何避免函数修改原对象?

答:

  1. 使用展开运算符创建浅拷贝:{ ...obj }
  2. 使用 Object.assign 创建浅拷贝
  3. 使用深拷贝(如 JSON.parse(JSON.stringify(obj))structuredClone

实战建议

  • ✅ 理解参数传递的本质,避免意外修改
  • ✅ 需要保护原对象时,使用拷贝
  • ✅ 注意浅拷贝和深拷贝的区别
  • ✅ 合理使用参数传递机制,提高性能
#前端面试##前端#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务