阅读本文请先查看执行上下文
首先, 介绍几个规范定义的概念:
- host
规范定义的比较抽象, 可简单理解为浏览器或Node这种宿主环境 - job
规范定义的比较抽象, 简单来说就是 host 的钩子 参考附录D
对于浏览器,setTimeout
,requestAnimationFrame
,Promise.then
等回调函数就是钩子
Jobs 执行规范
- 在某个时间点, 没有运行中的执行上下文且执行上下文栈为空, 那么执行:
- 执行 host 定义的准备操作
- 运行 job
- 执行 host 定义的清理操作, 在此之后, 执行上下文栈必须为空
- 在任何一个时间点, 至多执行一个 job
- 一个 job 开始后, 在其它 job 开始前必须执行完成
- job必须返回 NormalCompletion, 并且实现异常处理
job 的顺序由 host 决定, 对于浏览器环境, Promise-handling Jobs
(通常为Promise) 具有更高优先级
判断代码准备好执行的标准
- 执行上下文栈不为空
即待运行代码的执行上下文初始化完成 - 执行上下文栈最上面的执行上下文的
Realm
必须是 Realm Record
即Realm
初始化完成
浏览器事件循环模型
执行上下文栈遵循 LIFO(后进先出)
任务队列遵循 FIFO(先进先出)
普遍把浏览器任务队列分为:
-
MacroTask(宏任务)
主要包括setTimeout
,setInterval
,requestAnimationFrame
-
MicroTask(微任务)
主要包括Promise
本文从规范解读, 按规范分为 Promise-handling Jobs 以及其它 Jobs
代码分析
只分析执行顺序, 不详细介绍技术细节
function f1() {
console.log("f1");
}
function f2() {
console.log("f2");
}
function f3() {
console.log("f3");
}
function f4() {
const s = new Date().getSeconds();
while (true) {
if (new Date().getSeconds() - s >= 2) {
console.log("looped for 2 seconds");
break;
}
}
}
setTimeout(f1, 0);
Promise.resolve().then(f2).then(f3);
new Promise((resolve) => {
console.log("resolve");
resolve();
}).then(f4);
part1
创建执行上下文
执行上下文入栈
执行代码
执行 setTimeout(f1, 0)
, 设置一个定时器, 当定时器触发时(事实上, 定时有一个最小值, 所以设置时间为 0 的时候, 会以定时最小值触发), 会把回调函数 f1 推入 Jobs 队列
执行 Promise.resolve()
, 返回状态为 fulfilled
的 Promise
, 执行 .then(f2)
, 把回调函数 f2 推入 Promise-handling Jobs
执行 new Promise(f)
, f 在创建 Promise
实例时被执行, console.log("resolve")
=> 打印 resolve, resolve()
=> Promise
实例状态被置为 fulfilled
, Promise
实例执行 .then(f4)
, 把回调函数 f4 推入 Promise-handling Jobs
执行结束
执行上下文出栈
注意: 基本上, 一个函数执行前, 会创建相应的执行上下文. 执行 new Promise(f)
, Promise.resolve()
, setTimeout(f1, 0)
这些函数实际上都创建了相应的执行上下文用于执行代码, 这里未做详细讨论.
part2
当前满足 Jobs 执行规范, Promise-handling Jobs 具有高优先级, 检查不为空, 开始任务 f2
创建 f2 执行上下文
执行上下文入栈
执行代码
执行 console.log("f2")
, 打印 f2
执行结束
注意 .then(f2)
返回了一个状态为 fulfilled
的 Promise
, 执行 .then(f3)
, 把回调函数 f3 推入 Promise-handling Jobs
执行上下文出栈
part3
当前满足 Jobs 执行规范, Promise-handling Jobs 具有高优先级, 检查不为空, 开始任务 f4
创建 f4 执行上下文
执行上下文入栈
执行代码
执行 while
, 等待 2s 打印 looped for 2 seconds, 注意, 通过 setTimeout(f1, 0)
创建的定时器在最短时间被触发, f1 推入 Jobs
执行结束
执行上下文出栈
part4
当前满足 Jobs 执行规范, Promise-handling Jobs 具有高优先级, 检查不为空, 开始任务 f3
参考 part2, 打印 f3
part5
当前满足 Jobs 执行规范, Promise-handling Jobs 具有高优先级, 检查为空, 检查 Jobs, 检查不为空, 开始任务 f1
参考 part2, 打印 f1
end
所以最终打印:
resolve
f2
looped for 2 seconds
f3
f1