前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库默认参数求值时机 能否引用前面参数
JSJavaScript函数与作用域

JavaScript 函数默认参数的求值时机是什么?能否引用前面的参数?

默认参数在函数每次调用时求值,且按参数列表从左到右依次计算;后面的默认值可以引用前面已初始化的参数,但不能访问函数体内部的变量。

前端进阶之旅 · 一题精讲更新于 2026.09.05
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

参数作用域三层理解

  1. 调用时求值每次调用且实参为 undefined 时才重新计算默认值
  2. 从左到右后面的默认表达式可读取前面已初始化的参数
  3. 独立父作用域参数作用域是函数体作用域的父级,方向不可逆

判断口诀:前面参数可见、函数体内部不可见、外部词法环境可见。

核心回答

先记住这个答案

默认参数表达式在每次调用函数时才会求值,而不是定义时,并且只有对应实参为 undefined 时才触发。多个默认参数按从左到右的顺序初始化,因此后面的默认值可以引用前面的参数,例如 b = a * 2。参数列表形成一个独立的作用域,它是函数体作用域的父级,所以默认值里引用函数体内声明的变量或函数会抛出 ReferenceError,即便该变量用 var 声明也一样。

  • 默认参数在调用时求值,非定义时
  • 默认值从左到右,可引用前面参数
  • 默认值看不到函数体内的变量

求值时机与参数作用域的机制

默认参数表达式不会写在函数体里执行,而是在每次调用时、实参与形参绑定的阶段求值。触发条件是实参为 undefined:不传、显式传 undefined 都会走默认值,但 null、0、空字符串等其它假值不会触发。因为每次调用都重新求值,像 items = [] 这样的默认值每次都会创建新数组,不会出现 Python 那种默认对象被多次调用共享的坑。

规范为参数列表单独建了一个作用域,它是函数体作用域的父级。初始化按从左到右进行:先绑定 a,再算 b 的默认值,所以 b = a + 1 合法;反过来 a = b 而 b 在后面定义,此时 b 尚未初始化,会落入暂时性死区报 ReferenceError。函数体里用 var、let、function 声明的东西都活在子作用域,默认值朝父级方向查找,自然看不到它们。

默认值里可以放任意同步表达式,包括函数调用、模板字符串、解构默认值,甚至 this 和 arguments 也可访问。但不能使用 await 或 yield 暂停求值,参数初始化必须同步完成;在 async 函数里写 a = await p 会直接是语法错误,需要把等待逻辑挪到函数体内。

默认参数的求值顺序与可见性JavaScript
function demo(a, b = a * 2, c = b + 1) {
  return [a, b, c];
}
console.log(JSON.stringify(demo(3)));
console.log(JSON.stringify(demo(3, 10)));

let calls = 0;
function freshArray(value, list = []) {
  calls += 1;
  list.push(value);
  return list;
}
console.log(JSON.stringify(freshArray(1)));
console.log(JSON.stringify(freshArray(2)));
console.log(calls);

function outer(x, y = () => x) {
  var x = 99;
  return y();
}
console.log(outer(5));
查看输出与解释
[3,6,7]
[3,10,11]
[1]
[2]
2
5

演示从左到右求值、每次调用新建数组,以及函数体内的 var x 不会遮蔽参数 x,因为两者分属子作用域与父作用域,代码在标准 JavaScript 环境直接运行。

构造配置对象时串联默认值

场景:写一个创建请求配置的函数 buildConfig(url, method = "GET", headers = method === "GET" ? {} : { "Content-Type": "application/json" })。约束是调用方经常只传 url,且 POST 请求要自动带 JSON 头。决策是利用"后参数可引用前参数"的规则,让 headers 依赖 method 的已初始化值。结果:buildConfig("/api") 得到空 headers,buildConfig("/api", "POST") 自动带上 JSON 头,调用方无需手写条件逻辑。

这样处理的原因是把派生逻辑收敛到签名处,调用方一眼能看到参数的依赖关系,函数体里不再需要 if (headers === undefined) 这类补丁式赋值。代价是签名变长、默认表达式不宜塞太复杂的逻辑;一旦派生规则超过两三步,应该在函数体内用显式语句计算,否则默认值求值失败时堆栈定位不如函数体内直观,可读性也会下降。

容易踩错的失效条件

第一类失效是顺序搞反:a = b 而 b 在 a 之后声明,调用时触发暂时性死区报 ReferenceError,与 let 先使用后声明的表现一致。第二类是试图在默认值里读函数体内的变量,例如 f(a = helper()) 而 helper 是函数体内部声明的函数,调用时抛 ReferenceError,即使 helper 是会被提升的函数声明也不例外,因为参数作用域根本看不到子作用域。

第三类误区是把默认值当成定义时求值一次,例如把 Date.now() 或递增计数器放进默认值,然后惊讶于每次调用结果不同。对应处理:需要共享或延迟计算的引用类型,在函数体内用惰性初始化;需要引用函数体逻辑的,把默认值降级为 undefined 哨兵,再在函数体第一行显式判断。代价是多写几行代码,但语义明确,且不依赖参数作用域这种容易被忽略的机制。

回答前,多想一步

容易答错的地方

默认参数在函数定义时只求值一次
错误。默认表达式在每次调用时重新求值,且仅当实参为 undefined 时触发。写 items = [] 每次都会得到新数组,写计数函数调用也会每次执行,这与 Python 的默认参数共享行为完全不同。
默认值可以读到函数体内的 var 变量
错误。参数列表处于独立作用域,是函数体作用域的父级。函数体内的 var 虽然提升到函数体顶部,但只提升到子作用域,默认值向父级查找时看不到它,常见表现是拿到外层同名变量或抛 ReferenceError。
试着用自己的话回答

面试官还会怎么问?

传 null 会触发默认值吗?

不会。只有 undefined 触发默认值,null、0、false、空字符串都会原样绑定给形参。若需要 null 也兜底,应在函数体内用 nullish 合并运算符 ?? 处理。

默认参数对函数 length 属性有什么影响?

从第一个带默认值的参数开始,其自身及之后所有参数都不计入 length。例如 f(a, b = 1, c) 的 length 是 1,统计的是第一个默认参数之前的形参个数。

默认值里能写 await 吗?

不能。参数初始化必须同步完成,即使函数本身是 async 函数,a = await promise 也是语法错误。需要异步兜底时,把参数留空,在函数体内判断后再 await。

从一道题,走向一组知识

把知识连起来

函数与作用域

Function.prototype.bind 与 call/apply 的区别是什么?bind 返回的新函数有哪些特性?

同属「函数与作用域」专题,接着看 bind 与 call apply 区别 在具体场景中的处理方式。

函数与作用域

JavaScript 剩余参数(rest parameters)与 arguments 对象相比有哪些优势?

同属「函数与作用域」专题,接着看 rest 剩余参数与 arguments 区别 在具体场景中的处理方式。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • Default parameters

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 求值时机与参数作用域的机制
  3. 构造配置对象时串联默认值
  4. 容易踩错的失效条件
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑