前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库arguments.callee 严格模式禁用 替代方案
JavaScript函数与作用域

严格模式下为什么不能用 arguments.callee?递归该怎么写?

严格模式下读 arguments.callee 会抛 TypeError。它当年是为了让匿名函数能递归调用自己,但它依赖运行时的 arguments 对象,让引擎很难做内联优化。现在的替代方案是给函数起个名字:函数声明,或者具名函数表达式 const f = function fact() {}。

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

递归时,函数怎么找到自己

  1. arguments.callee运行时从参数对象里取当前函数,严格模式下禁用
  2. 具名函数表达式function fact() {} 里的 fact 只在函数体内可见,指向不会变
  3. 外层变量箭头函数只能靠它递归,但变量被重新赋值后递归就断了

ES 模块和 class 的代码天然是严格模式,不用写 'use strict',在这些地方用 arguments.callee 照样会报错。

核心回答

先记住这个答案

arguments.callee 指向当前正在执行的函数,早年是为了让匿名函数能递归调用自己。ES5 的严格模式禁用了它,一读就抛 TypeError。主要原因有两个:一是它要求引擎在运行时随时能从 arguments 对象拿到当前函数,妨碍了函数内联等优化;二是和它配套的 caller 会暴露调用栈,有安全隐患。替代方案很简单,给函数起个名字:用函数声明,或者用具名函数表达式,函数体里通过这个名字递归。

  • 严格模式读 arguments.callee 直接抛 TypeError
  • 被禁的主要原因是妨碍引擎内联和优化
  • 替代方案是函数声明或具名函数表达式
  • 具名函数表达式的名字只在函数体内可见

先看严格模式下会发生什么

arguments.callee 在非严格模式下还能用,严格模式下读它会直接报错,不是返回 undefined。

同样的读取,两种模式的结果JavaScript
function sloppy() {
  return arguments.callee === sloppy // 非严格模式:callee 指向函数自己
}

function strict() {
  'use strict'
  return arguments.callee // 严格模式:一读就抛错
}

console.log(sloppy())
try {
  strict()
} catch (e) {
  console.log(e.name)
}
查看输出与解释
true
TypeError

严格模式下的 arguments 对象上,callee 被定义成一个访问器属性,读和写都会抛 TypeError。这样设计是故意的:如果只是返回 undefined,老代码可能悄悄走错分支,直接报错更容易发现问题。 要注意,ES 模块和 class 里的代码默认就是严格模式。在 Vite、Webpack 打包的现代项目里写 arguments.callee,即使文件里没有 'use strict' 也会报错。

它当初解决什么问题,又为什么被禁

在 ES3 时代,匿名函数想递归调用自己没有好办法。比如 setTimeout(function () { ... }, 1000) 里想再次调用这个匿名函数,只能写 arguments.callee。当时虽然也可以给函数表达式起名字,但老版本 IE 对具名函数表达式有 bug,会把名字泄漏到外层作用域,所以很多代码都用 callee。

问题出在性能上。引擎优化函数时,常见手段是内联:把被调用函数的代码直接展开到调用处,省掉一次函数调用。可一旦函数体里用到 arguments.callee,引擎就必须保证运行时能拿到「当前函数对象」这个引用,内联后这个引用就很难提供。为了支持这一种极少用到的写法,引擎只能放弃优化。

另一个问题是 fn.caller,以及早已从规范里移除的 arguments.caller。它们能拿到调用当前函数的那个函数,等于让任何函数都能往上翻调用栈,拿到不该拿到的引用。严格模式把这一组能力一起禁掉了。

递归的正确写法:给函数起名

函数声明本身就有名字,递归直接用。需要写成表达式的时候,比如作为回调、赋值给变量,就用具名函数表达式。

外层变量递归和具名函数表达式对比JavaScript
let countdown = function (n) {
  return n === 0 ? 'done' : countdown(n - 1) // 通过外层变量递归
}
const saved = countdown
countdown = null // 外层变量被改掉了
try {
  saved(3)
} catch (e) {
  console.log(e.name) // 递归时找不到函数
}

const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1) // fact 只在函数体内可见
}
const alias = factorial
console.log(alias(5)) // 换个名字调用也没问题
console.log(typeof fact) // 外面访问不到 fact
查看输出与解释
TypeError
120
undefined

第一种写法看起来也能递归,但它依赖外层变量 countdown。一旦这个变量被重新赋值,函数内部递归调用的就是 null,抛出 TypeError。 具名函数表达式里的 fact,是一个只在函数体内部存在的绑定,永远指向这个函数本身。外面的变量怎么改都不影响它,外面也访问不到它,不会污染作用域。这正是 arguments.callee 当年想提供的能力,现在用语法就能做到。

定时器里的匿名函数递归
轮询场景里经常要在回调中再次调用自己。以前写 setTimeout(arguments.callee, 1000),现在给回调起个名字就行:setTimeout(function tick() { poll(); setTimeout(tick, 1000) }, 1000)。tick 这个名字还会出现在错误堆栈里,比 anonymous 好排查得多。
回答前,多想一步

容易答错的地方

以为去掉 'use strict' 就能继续用
现代项目里的代码大多是 ES 模块,或者在 class 里,这些地方天然是严格模式,删掉 'use strict' 没有用。就算在普通脚本里能用,也只是把性能问题保留下来了。
以为箭头函数可以用 callee
箭头函数没有自己的 arguments,写 arguments.callee 拿到的是外层普通函数的 arguments,指向的也是外层函数,不是箭头函数本身。箭头函数要递归,只能通过外层变量,所以复杂递归更适合用普通函数写。
以为具名函数表达式的名字可以重新赋值
在函数体里给 fact 赋值,非严格模式下会静默失败,fact 还是原来的函数;严格模式下直接抛 TypeError。这个名字是只读的,这也是它比外层变量可靠的原因。
试着用自己的话回答

面试官还会怎么问?

严格模式对 arguments 还有哪些改动?

除了禁用 callee,最重要的是 arguments 不再和形参联动。非严格模式下,简单参数列表的函数里改 arguments[0] 会同时改掉第一个形参,严格模式下两者互不影响。另外严格模式下不能给 arguments 这个名字重新赋值,也不能用它当变量名。

具名函数表达式的名字和 fn.name 是什么关系?

fn.name 会优先取具名函数表达式自己的名字。const factorial = function fact() {} 的 factorial.name 是 'fact'。如果是匿名函数表达式,引擎会根据赋值的变量名推断,const foo = function () {} 的 foo.name 是 'foo'。

现在还有什么场景会遇到 arguments.callee?

主要是在维护很老的脚本,或者读一些早期的库源码时。改造时直接给函数起名,把 arguments.callee 换成这个名字即可。如果是 ESLint 项目,no-caller 规则会直接报出这类用法。

从一道题,走向一组知识

把知识连起来

函数与作用域

异步生成器和普通生成器有什么区别?什么时候会用到它?

异步生成器函数和箭头函数一样,都是 ES6 之后的新函数形式,它们从一开始就不需要 callee 来引用自己。

函数与作用域

JavaScript 传参是按值传递还是按引用传递?传对象进去改了会怎样?

非严格模式下改 arguments[0] 会连带改掉形参,很容易被误认为是按引用传参,那一题讲清参数到底怎么传。

参考资料

  • JavaScript:函数与作用域参考资料

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

本题目录
  1. 先记住这个答案
  2. 先看严格模式下会发生什么
  3. 它当初解决什么问题,又为什么被禁
  4. 递归的正确写法:给函数起名
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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