一、核心线程
浏览器是一个多进程多线程的系统,它会有一个主进程进行任务的调度。有一个第三方插件进程,避免插件崩溃时影响页面内容。然后每个 tab 页面都是一个单独的进程,每个 tab 进程中又有以下几个重要的线程:
- GUI 渲染线程
- JS 引擎线程
- 定时器线程
- 异步请求线程
- 事件触发线程
1.1 GUI 渲染线程
- 主要负责页面的渲染,包括解析 HTML,CSS,构建 DOM 树 -> CSS 规则树 -> 渲染树,最后计算元素的位置、渲染,回流、重绘等
- 当执行 JS 引擎线程时,GUI 渲染线程会被挂起,当任务队列空闲时,GUI 线程恢复执行
1.2 JS 引擎线程
- 该线程主要负责解析、执行 js 脚本
1.3 定时器线程
- 该线程负责执行定时器任务,如 setTimeout、setInterval(如果放在主线程计时,受限于 js 单线程,计时会不准)
- 主线程逐行执行代码到异步任务时,会将定时器任务交给该线程处理。等到计时结束,事件触发线程会将回调函数加入到任务队列尾部,等待 JS 引擎线程执行
1.4 异步请求线程
- 负责执行异步请求,如 ajax、promise、http
- 主线程逐行执行代码到异步请求时,会异步任务交给该线程处理。等到异步任务的状态码变更时,事件触发线程会将回调函数加入到任务队列尾部,等待 JS 引擎线程执行
1.5 事件触发线程
主要负责将准备好的事件加入到任务队列,如 setTimeout 回调、ajax 回调
二、事件循环 Event Loop
2.1 宏任务与微任务
先来道题
console.log('开始')
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)
先揭晓答案
- 开始
- Promise1
- setTimeout1
- Promise2
- setTimeout2
这是视频讲解
JS 中的事件循环
这里注意以下几点即可
- 首先遇到同步任务,压入执行栈
- 遇到微任务压入微任务队列
- 遇到宏任务压入宏任务队列
- 所有的同步任务相当于一个宏任务,而一个宏任务执行完成之后,需要把微任务队列中的全部任务全部执行
三、Node 中的事件循环
先注意一点,不管是浏览器中的事件循环还是 node 中的事件循环,都不是 JS 引擎去实现的,而是浏览器和 node 自己实现的东西。
所以即使 chrome 和 node 都用了 V8 引擎,可是二者的事件循环却完全不是一回事,实现的逻辑也是大相径庭。
node 中的事件循环是在 libuv 中实现的,libuv 是一个聚焦于 I/O 的库,一个基于事件驱动的跨平台抽象层,封装了不同系统底层一些特性,对外提供统一的 API。
node.js 的运行机制大致如下:
V8 引擎解析 js 代码
调用 node API
libuv 负责执行 node API,