浏览器架构和事件循环
chrome浏览器架构
Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。
- Browser Process:
负责包括地址栏,书签栏,前进后退按钮等部分的工作;
负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;
- Renderer Process:
- 负责一个 tab 内关于网页呈现的所有事情
- Plugin Process:
- 负责控制一个网页用到的所有插件,如 flash
- GPU Process
- 负责处理 GPU 相关的任务
由于一个tab标签页都有一个独立的渲染进程,所以一个tab异常崩溃后,其他tab不会受到影响。
一个渲染进程包括
- JS引擎线程
- HTTP请求线程
- 定时触发线程
- 事件触发线程
- GUI线程
浏览器JS异步执行原理
执行JS代码的线程只有一个,是浏览器提供的JS引擎线程,浏览器中还有
浏览器中的事件循环
js引擎解析代码时,遇到同步任务->放入执行栈直接执行,遇到异步任务(比如ajax),交给网络线程处理,完成后将对应回调放入异步任务队列,当执行栈清空时,循环检测异步任务队列,将异步队列中的回调放入执行栈中执行。
宏任务和微任务
Dom渲染会在微任务结束后,宏任务执行前
宏任务
# | 浏览器 | Node |
---|---|---|
I/O |
✅ | ✅ |
setTimeout |
✅ | ✅ |
setInterval |
✅ | ✅ |
setImmediate |
❌ | ✅ |
requestAnimationFrame |
✅ | ❌ |
微任务
# | 浏览器 | Node |
---|---|---|
process.nextTick |
❌ | ✅ |
MutationObserver |
✅ | ❌ |
Promise.then catch finally |
✅ | ✅ |
事件循环加入宏任务微任务
- Call Stack调用栈清空
- 清空当前微任务队列,若有新增微任务,会加入当前队列尾部继续执行。
- 尝试DOM渲染
- 执行event loop
- 取出任务队列里的一个宏任务,入栈执行
- 返回1
对于以下demo
1 | new Promise((r) => { |
Promise.prototype.then()
会隐式返回一个新 Promise
如果 Promise 的状态是 pending,那么 then
会在该 Promise 上注册一个回调,当其状态发生变化时,对应的回调将作为一个微任务被推入微任务队列
如果 Promise 的状态已经是 fulfilled 或 rejected,那么 then()
会立即创建一个微任务,将传入的对应的回调推入微任务队列
对于以下demo:
1 | console.log('start') |
Node中的事件循环
- 执行同步代码
- 执行nextTick队列里process.nextTick回调
- 执行微任务队列Promise.then里的回调
- 执行Timer队列里setTimeout,setInterval回调
- 执行Poll队列里文件IO,网络IO相关操作
- 执行Check队列里setImmediate回调
- 在Poll队列等待,判断是否有新的异步任务,若Poll队列有任务,直接执行,若其他队列有任务,则继续下一个tick
Node中异步API分类
- 定时器API:setTimeout、setInterval(Timer队列)
- IO操作:文件读写、数据库操作、网络请求(Poll队列)
- node独有:process.nextTick、setImmediate(Check队列)
Timer-> Poll -> Check(称之为一个Tick)
到Poll队列时,会暂停等待另外两个队列,此时如果有新的IO操作,会直接执行(因为Node的设计就是优先处理IO操作)
setTimeout和setImmediate的问题
1 | setTimeout(() => { |
setTimeout在node中延迟时间最小取值是1ms,而不是0ms(最快1ms调用),所以这里谁先输出是不一定的,若要让“setImmediate”先执行,可以包在文件IO的回调里
1 | fs.readFile(_filename, () => { |
process.nextTick(nextTick队列)
nextTick队列,总会在下个Tick运行之前执行
微任务队列
nextTick->微任务->Timer->Poll->Check