题目 26 / 31

为什么这些输出是立即执行的,而不是延迟之后才执行?

知识点: this 绑定难度: 高级

函数实参会在接收它们的那次调用真正运行之前先被求值——因此写成实参的 .call() 会立刻执行,而不是等到之后。

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

预测输出结果

预测输出。回调函数是内联写的,并且附加了 .call(obj1)——setTimeout 还会先等待 0 毫秒才执行它吗?

var obj1 = { a: 1 };
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a);
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this);
      console.log(this.a);
    }.call(obj1), 0);
  }
};
var a = 3;

obj2.foo1();
obj2.foo2();
查看答案与完整解析

正确输出

2
{ a: 1 }
1

为什么会这样

obj2.foo1() 是一次普通的成员调用:隐式绑定让 this 指向 obj2,因此 this.a 立即读到 2。

接下来运行 obj2.foo2()。在 setTimeout 本身能够被调用之前,按照 EvaluateCall 的规定,它的实参必须先被求值——而这里的第一个实参并不是一个裸函数,而是 CallExpression function () {...}.call(obj1)。对这个实参求值,意味着要立刻、同步地调用它,并把 this 显式强制为 obj1,这正是 console.log(this) 和 console.log(this.a) 会立即运行、打印出 { a: 1 } 和 1 的原因——完全没有任何延迟。

这个内部调用没有 return 语句,因此它的求值结果是 undefined,而这才是 setTimeout 实际收到的第一个实参的值。一个不是函数的处理器之后永远不会被触发,因此延迟结束后什么都不会再发生。

这道题考察什么

  • 实参表达式会在它所属的那次调用真正运行之前先被求值
  • 写成实参的 .call()/.apply() 表达式会在它出现的语法位置立即执行
  • setTimeout 只会调度它第一个实参求值之后得到的那个值——在这里,这个值是 undefined,而不是一个函数

运行环境前提

这个函数没有显式 return,求值结果是 undefined,这正是之后 setTimeout 变成空操作的原因;任何标准的定时器实现都会把不可调用的处理器当作无效处理。

规范参考

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

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