搜尋文章

前端面试 P081:StrictMode 的检查与版本边界JavaScript 函数传参:值传递与对象引用前端面试 P018:手写柯里化与参数个数边界前端面试 P010:普通函数、箭头函数与 this前端面试 P088:Immer 草稿与结构共享前端面试 P006:对象遍历与属性键的选择前端面试 P009:执行上下文、调用栈与词法作用域前端面试 P094:SSE 分片解码与事件边界前端面试 P097:请求并发池、任务启动与失败释放前端面试 P076:Fiber 的协作调度与不可中断边界前端面试 P013:节流、防抖与取消待执行任务前端面试 P033:Object.create、描述符与共享属性前端面试 P057:Vue Router 配置、历史模式与导航前端面试 P030:构造函数与 new 的返回规则前端面试 P077:key、组件身份与状态重置前端面试 P039:同源策略、CORS 与请求是否发出前端面试 P032:instanceof、原型链与自定义判断前端面试 P047:Proxy 与 Vue 2 属性拦截的区别前端面试 P019:函数组合与同步数据管道前端面试 P004:手写深拷贝、循环引用与实现边界前端面试 P059:导航守卫、顺序与权限边界前端面试 P083:函数式更新、队列与纯度前端面试 P014:call、apply、bind 与模拟实现边界前端面试 P012:高阶函数、柯里化与部分应用前端面试 P041:事件委托、目标匹配与 React 事件前端面试 P048:track、trigger、动态依赖与清理前端面试 P066:自定义指令与 DOM 资源清理前端面试 P075:props 只读、状态归属与派生数据前端面试 P062:Pinia 创建、实例范围与使用前端面试 P073:JSX 编译、React 元素与表达式前端面试 P026:Promise 延迟与顺序任务队列前端面试 P016:纯函数、副作用与参数共享前端面试 P094:流式文本的 SSE 与 WebSocket 取舍前端面试 P064:storeToRefs 与 action 解构纠错前端面试 P044:Composition API 与组合式函数前端面试 P051:副作用清理与过期请求前端面试 P021:事件循环、微任务与渲染机会前端面试 P003:原始值、对象共享与赋值行为前端面试 P098:用户偏好存储、TTL 与命名空间前端面试 P056:插槽作用域与 slot props前端面试 P011:闭包、变量绑定与循环回调前端面试 P027:async 错误处理与 return await前端面试 P036:CSP、nonce 与防护边界前端面试 P007:隐式转换、ToPrimitive 与空数组比较前端面试 P035:继承方式对照与寄生组合式继承前端面试 P025:手写 Promise 的最小状态机前端面试 P079:受控表单与非受控输入前端面试 P072:虚拟 DOM 的价值与性能边界前端面试 P005:数组方法、可变性与回调副作用前端面试 P100:Proxy 沙箱为何不是安全边界前端面试 P040:发布订阅、事件总线与退订前端面试 P053:props、组件事件与 v-model 约定前端面试 P008:相等比较、NaN 与 Object.is前端面试 P065:Pinia 最小变更与对象替换的取舍前端面试 P087:Redux 单向数据流与状态边界前端面试 P002:数据类型与 typeof 的陷阱前端面试 P085:useReducer 与 useState 的实际取舍前端面试 P078:React 合成事件与旧事件池纠错前端面试 P025:Promise 解析过程与实现验收前端面试 P043:Vue 渐进式与声明式 UI前端面试 P091:SWR、TanStack Query 与服务端缓存前端面试 P074:函数组件快照与类实例的输出纠错前端面试 P071:UI 是状态映射与渲染纯度前端面试 P054:provide、inject 与跨层级依赖前端面试 P061:何时引入全局状态管理前端面试 P097:请求去重、缓存键与过期策略前端面试 P086:Context 拆分与订阅更新边界前端面试 P024:Promise 并发组合与空输入边界前端面试 P089:Redux middleware、next 与异步返回值前端面试 P022:Promise 状态、链式调用与错误传播前端面试 P084:状态提升、单一来源与范围选择前端面试 P034:class、super 与语法糖的边界前端面试 P058:路由参数、query 与组件 props前端面试 P020:定时器延迟、轮询重叠与清理前端面试 P029:对象原型、prototype 与属性查找前端面试 P095:AI SDK、编排框架与生成 UI 工具前端面试 P092:Signals、细粒度依赖与 React 集成边界前端面试 P046:Vue 响应式陷阱与 ref 自动解包纠错前端面试 P090:Zustand、Jotai 与状态作用域前端面试 P100:微前端 JS 隔离方案与资源生命周期前端面试课程:学习目录与纠错记录前端面试 P023:async、await、异步恢复与并发前端面试 P068:Vue 2 与 Vue 3 的列表 diff 区别前端面试 P015:IIFE、作用域隔离与现代模块前端面试 P063:Pinia 状态、getter 与 action前端面试 P052:Vue 条件、列表与表单指令前端面试 P050:Vue 生命周期、DOM 时机与 SSR前端面试 P060:路由 meta、合并与界面配置前端面试 P045:ref、reactive、computed 与 watch前端面试 P096:令牌无感刷新、重试与安全边界前端面试 P093:MCP 架构与前端集成场景前端面试 P080:组合、children 与自定义 Hook前端面试 P082:useState 更新请求、快照与批处理前端面试 P067:Vue 模板编译与运行时边界前端面试 P031:手写 new 与 Reflect.construct 的区别前端面试 P069:nextTick 等待 DOM 刷新与绘制的区别前端面试 P055:默认插槽、具名插槽与内容接口前端面试 P038:嵌套定时器的最小延迟与调度选择前端面试 P099:倒计时截止时间、漂移与完成事件前端面试 P019:记忆化缓存与递归调用的纠错前端面试 P049:Vue 与 React 的更新范围和批处理前端面试 P004:浅拷贝、深拷贝与 structuredClone前端面试 P042:Vue 面试导学与学习顺序前端面试 P096:共享刷新 Promise 与并发失败恢复前端面试 P070:React 面试导学与复习主线前端面试 P028:异步输出顺序的逐步推演前端面试 P001:var、let、const 与暂时性死区前端面试 P037:内存泄漏的快照与引用链排查前端面试 P017:手写 bind、重复绑定与构造调用你好,這裡是 Henry 的自留地給想法找一個落腳的地方,也給那些解決過的問題留下一點記錄。
Esc 關閉
全部文章

JavaScript 函数传参:值传递与对象引用

目前顯示中文(中國)原文

核心结论

JavaScript 的函数参数按值传递。形参是独立的局部变量,不是调用方变量的别名。

对于对象,参数得到的是与实参相同的对象值。可以把这个过程理解为“复制一份指向对象的引用”:没有复制整个对象,所以函数内外能够访问同一个对象;两个变量又彼此独立,所以给形参重新赋值不会改变外部变量的指向。

原题与结果

function modify(obj) {
  obj.name = 'Modified';
  obj = { name: 'New Object' };
}

let myObj = { name: 'Initial' };
modify(myObj);

console.log(myObj.name); // 'Modified'

按执行顺序理解

  1. let myObj = { name: 'Initial' } 创建对象 A,让 myObj 指向它。
  2. modify(myObj) 初始化局部参数 obj。obj 和 myObj 是两个变量,但此时指向同一个对象 A。
  3. obj.name = 'Modified' 修改对象 A 的属性。通过 myObj 读取同一个对象,也会看到这个变化。
  4. obj = { name: 'New Object' } 创建对象 B,并让局部参数 obj 指向对象 B。这一步没有给 myObj 赋值。
  5. 函数结束后,myObj 仍然指向对象 A,因此输出 'Modified'。

刚进入函数时:

myObj ──→ 对象 A { name: 'Initial' }
obj   ──→ 对象 A(同一个对象)

执行 obj.name = 'Modified' 后:

myObj ──→ 对象 A { name: 'Modified' }
obj   ──→ 对象 A(同一个对象)

执行 obj = { name: 'New Object' } 后、函数返回前:

myObj ──→ 对象 A { name: 'Modified' }
obj   ──→ 对象 B { name: 'New Object' }
操作 修改的内容 外部变量的表现
obj.name = 'Modified' 共享对象的属性 myObj.name 读到 'Modified'
obj = { name: 'New Object' } 局部参数 obj 的绑定 myObj 继续指向原对象

为什么传入对象引用,仍然叫按值传递

容易产生的疑问是:“传进去的明明是引用,为什么还叫值传递?”

这里有两个不同层面:“传入的内容是什么”,以及“参数通过什么机制接收它”。

说法 含义
传入对象的引用值 两个独立变量指向同一个对象
按引用传递参数 形参成为调用方变量的别名,操作的是同一个变量

“按值传递”强调复制值,不要求这个值必须是数字或字符串。即使把对象引用作为值传入,也不意味着形参成为外部变量的别名。

可以把原题调用时的关键效果理解成下面这次赋值;这只是解释模型,不是函数调用的完整展开:

let obj = myObj;

这会初始化一个独立的变量 obj。给它重新赋值,不等于给 myObj 重新赋值。

如果是严格意义上的按引用传递,obj 就相当于 myObj 的另一个名字。那么 obj = { name: 'New Object' } 也会改变 myObj,原题应输出 'New Object'。JavaScript 实际输出 'Modified',说明形参与外部变量并不是这种别名关系。

修改共享对象的属性,并不能证明参数按引用传递。对象的这种共享行为,也常被称为按共享传递(call by sharing)。

引用、地址与对象的区别

  • 对象是被访问的那份数据,例如 { name: 'Initial' }。
  • 引用可以类比为找到对象的地址;它不是对象本身。
  • 两个变量可以指向同一个对象,不需要再增加一层“对象值的引用才是真正的对象”。

“复制地址”只是帮助理解的模型。JavaScript 不直接向普通代码暴露对象的实际内存地址;描述参数传递时,也不需要依赖引擎如何安排内存。

基本类型同样按值传递

function change(num) {
  num = 100;
}

let count = 10;
change(count);

console.log(count); // 10

参数 num 最初得到数值 10。给局部变量 num 重新赋值,不会改变外部变量 count。原题中给 obj 重新赋值也是一样的道理,只是对象参数还具有共享对象这一特点。

如何让外部变量接收新对象

函数返回新对象,再由调用方明确赋值:

function modify(obj) {
  obj.name = 'Modified';
  obj = { name: 'New Object' };
  return obj;
}

let myObj = { name: 'Initial' };
myObj = modify(myObj);

console.log(myObj.name); // 'New Object'

这里改变 myObj 指向的是调用方的 myObj = modify(myObj),并不是函数内部给参数赋值的操作。

来源

2026-10-26 的 JavaScript 学习讨论,根据原题和关于值传递、对象引用的追问整理。

本题也出现在这套课程的 P3 · 08:51 函数参数传递机制。本集关于对象赋值、const 与相等比较的完整笔记见 前端面试 P003:原始值、对象共享与赋值行为。

看看其他文章 →