study
study copied to clipboard
Javascript 单线程模型 Event Loop 机制
Reference
浏览器相关
-
每一个 Tab 都是浏览器的一个进程
同域名 Tab 之间可通过 Web Storage API 进行 跨标签通讯
-
Javascript 是单线程的,但浏览器是多线程的:
- Javascript 引擎 线程
- GUI 渲染引擎线程(浏览器内核)
- 事件触发线程
- HTTP 网络线程
- 定时器线程
- 事件轮询线程(Event Loop)
- ...
需要注意的是 JavaScript 引擎线程 与 GUI 渲染线程互斥! 因为 Javascript 可操作 DOM,同时执行可能出现渲染错误。
Javascript 运行机制
Event Loop
为了提高运行效率,Javascript 区分 同步任务 和 异步任务,同步任务在主线程运行,形成 执行栈 (execution context stack),异步任务只有当有了结果之后才会进入 任务队列 (task queue),当主线程所有任务执行完毕才会去任务队列获取异步任务,这个过程不断重复,形成 Javascript 的运行机制 Event Loop。
图片来源 - 阮一峰 blog 文章 - JavaScript 运行机制详解:再谈Event Loop
异步任务如何产生?
- HTTP 请求
- 用户操作 (鼠标点击、页面滚动等)
- 定时器 (
setTimeout
setInterval
) - ...
每一个异步任务对应的是一个回调函数,如果某个用户操作(比如 click
)事件没有对应的回调函数,那也就不会往任务队列里面加入任务。
关于定时器需要注意的是:定时器的任务会在给定时间之后加入任务队列,但是必须得等执行栈所有的同步任务执行完和任务队列靠前的异步任务执行完成才会执行,也就是说定时器只是定时加入任务队列,而不是定时执行。
比如 setTimeout(func, 0)
是立即往任务队列添加 func
任务,并不是立即执行 func
函数,只有当执行栈为空并且任务列表此任务之前没有其它异步任务,那 func
函数就会被执行栈调用并且立即执行。
Important!!!
有个细节知识点可能不为大众所知(我也曾是大众中的一员),一个浏览器环境只能有一个事件循环,而事件循环可以有多个任务队列!
多个任务队列?
涉及两个概念:macro-task queue(宏任务队列)) 和 micro-task queue(微任务队列)
- macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
- micro-task: process.nextTick, Promises(这里指浏览器实现的原生 Promise), Object.observe, MutationObserver
上面只是概念,那多个任务队列是按照什么顺序(如何)运行的?
来个例子:
console.log(1)
setTimeout(function () {
console.log(2)
}, 0)
new Promise(function (resolve, reject) {
console.log(3)
resolve('resolve')
}).then(function () {
console.log(4)
})
console.log(5)
首先,以上所有代码是一个 macro-task,也就是 macro-task 概念中的 script(整体代码),然后:
-
浏览器先执行 script macro-task,执行过程中创建了新的 macro-task
setTimeout
和 micro-taskPromise
,这两个任务被挂起,浏览器打印出 1、3、5。 -
浏览器再执行 micro-task,将 micro-task queue 内的所有任务取出执行,此例中包含一个
Promise
任务,浏览器打印出 4。 -
浏览器再执行 macro-task queue 的下一个任务,此例中包含一个
setTimeout
任务,浏览器打印出 2。
循环 2、3 步骤
深入其中,看下面的问题:
macro-task queue 内的任务每次取出一个执行,一个 macro-task 执行过程中可能产生多个 macro-task 和多个 micro-task,执行完毕后接着把这多个 micro-task 全部顺序执行,这些 mirco-task 执行过程中也可能产生多个 macro-task 和多个 micro-task,浏览器会如何处理?
- 对于 macro-task 当然是继续往 macro-task queue 内添加任务,等待浏览器下一次的调用。
- 对于 micro-task 则是继续往 micro-task queue 内添加任务,直到 micro-task queue 所有任务执行完成再去调用 macro-task queue 的下一个任务。