题目 03 / 14

函数读取的是调用者的变量,还是定义时所在作用域的变量?

知识点: 词法作用域

JavaScript 采用词法(静态)作用域,函数的作用域链在定义时就已确定,与谁调用它无关。

使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。

预测输出结果

预测输出结果。foo 在 bar 内部执行时,读取的是外层 x,还是 bar 的局部 x?

var x = 10;
function foo() {
    console.log(x);
}
function bar() {
    var x = 20;
    foo();
}
bar();
查看答案与完整解析

正确输出

10

为什么会这样

foo 和 bar 都声明在全局作用域,因此 foo 的外部环境引用始终指向全局环境,无论是谁调用了 foo。

调用 bar() 会创建自己的局部 x,值为 20,但这个绑定只存在于 bar 自己的环境中,从未加入 foo 的作用域链。

在 bar() 内部调用 foo() 时,foo 仍然沿着自己的作用域链查找 x:先局部(没有),再全局,找到值为 10 的 x。调用位置不会改变这条作用域链,因此输出结果是 10。

这道题考察什么

  • 词法作用域
  • 作用域链在定义时确定,而非调用时
  • JavaScript 没有动态作用域

运行环境前提

这是任意 JavaScript 环境下标准的词法作用域行为;JavaScript 从不使用动态作用域。

规范参考

JavaScript 代码编辑器支持直接编辑或粘贴自定义代码
第 0 步 / 共 0 步
单步Space播放
按 → 单步 或 Space 自动播放
阶段 1: 编译预解析(变量提升与内存分配)
阶段 2: 代码执行(逐行赋值与调用求值)
单步执行原理解析

按键盘右方向键 “→” 进行单步调试,或按空格键 “Space” / 点击 “▶ 自动播放” 进行连续执行。

作用域链 (Scope Chain)
词法作用域链: 当前 AO → 外部 [[Scope]] → 全局 (GO)
暂无激活的作用域链
调用栈 (CALL STACK)执行上下文栈 (后进先出 LIFO)
后进先出 (LIFO) 栈帧
全局执行上下文 (GEC)
▶ 活动栈帧
堆内存空间 (HEAP & MEMORY SPACE)已分配对象 (GO, AO, FunctionObjects, ER)
ECMAScript 堆内存区
内存中暂无分配对象
控制台输出 (Console)
console.log(...) Stream
暂无输出产生