浏览器架构和事件循环
Chrome浏览器架构
Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。
Browser Process
- 子进程的管理(网络、渲染、GPU)
- 负责包括地址栏,书签栏,前进后退按钮等部分的工作;
- 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;
Network Service
- 负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务
Renderer Process
- 负责一个 tab 内关于网页呈现的所有事情,进程启动后,会开启一个渲染主线程,负责执行HTML、CSS、JavaScript代码
Plugin Process
- 负责控制一个网页用到的所有插件,如 flash
GPU Process
- 负责处理 GPU 相关的任务
由于一个tab标签页都有一个独立的渲染进程,所以一个tab异常崩溃后,其他tab不会受到影响。
一个渲染进程包括
- 主线程
- HTTP请求线程
- 定时触发线程
- 事件触发线程
- GUI线程
渲染进程中的主线程是如何工作的
包括但不限于
- 解析HTML
- 解析CSS
- 计算样式
- 布局
- 处理图层
- 每秒把页面绘制60次
- 执行全局js代码
- 执行事件处理函数
- 执行计时器回调函数
浏览器JS异步执行原理
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
- 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
- 主线程不断重复上面的第三步。
浏览器中的事件循环
事件循环是浏览器渲染主线程的工作方式,主线程会将不同的任务交给不同线程处理,其他线程处理完后会把回调函数放到对应的队列里

Event Loop(事件循环)中,每一次循环称为 tick, 每一次 tick 的任务如下:
- 调用栈执行同步代码,遇到异步任务交给各自的线程处理(其他线程处理完后,会把异步任务的回调加到对应的任务队列中)
- 调用栈清空后,检查
Microtask队列是否存在Microtask,如果存在则不停的执行,直至清空Microtask队列 - 更新render,dom渲染(每一次事件循环,浏览器都可能会去更新渲染)
- 从
Task队列中取出一个Task执行 - 重复以上步骤
任务队列(Task Queue)
延时任务队列(Task)
- 定时器API
setTimeout和setInterval相应的回调函数
交互任务队列
一段新程序或子程序被直接执行时(比如从一个控制台,或在一个 script 元素中运行代码)。
触发了一个事件,将其回调函数添加到任务队列时,比如交互相关(用户点击、视口变化、键盘事件)
优先级:用户交互相关任务 > 定时任务 > 网络 / 通信任务。
script标签UI交互(click、mousedown、keydown、touchstart 等)网络I/O(XHR/Fetch 完成回调)setTimeout、setIntervalrequestAnimationFrameMessageChannel 的 onmessage 回调
微任务队列(MicroTask Queue)
MutationObserverPromise.then catch finallyqueueMicrotask()
对于以下demo
1 | new Promise((r) => { |
Promise.prototype.then() 会隐式返回一个新 Promise
如果 Promise 的状态是 pending,那么 then 会在该 Promise 上注册一个回调,当其状态发生变化时,对应的回调将作为一个微任务被推入微任务队列
如果 Promise 的状态已经是 fulfilled 或 rejected,那么 then() 会立即创建一个微任务,将传入的对应的回调推入微任务队列
对于以下demo:
1 | console.log('start') |
1 | console.log(1); |
Node中的事件循环
Node中的任务队列和异步API
- Timer队列:定时器相关API,
setTimeout、setInterval - Poll队列:IO操作,文件读写、数据库操作、网络请求
- Check队列:
setImmediate(node环境中独有) - NextTick队列:
process.nextTick(node环境中独有)
Node中事件循环执行流程

- 调用栈执行同步代码,遇到异步任务交给各自的线程处理(其他线程处理完后,会把异步任务的回调加到对应的任务队列中)
- 调用栈清空后,执行nextTick队列里
process.nextTick回调,直至清空nextTick队列 - 清空微任务队列中
Promise.then里的回调 - 从Timer队列里取出
setTimeout,setInterval回调执行,若nextTick队列和微任务队列不为空,重复步骤2/3 - 从Poll队列里取出文件IO,网络IO相关操作的回调执行,若nextTick队列和微任务队列不为空,重复步骤2/3
- 从Check队列里取出
setImmediate回调执行,若nextTick队列和微任务队列不为空,重复步骤2/3 - 在Poll队列等待,判断是否有新的异步任务,若Poll队列有任务,直接执行,若其他队列有任务,则继续下一个 Tick
Timer-> Poll -> Check称之为一个Tick
nextTick队列,总会在下个Tick运行之前执行
遇到Poll队列时,会暂停等待另外两个队列,此时如果有新的IO操作,会直接执行(因为Node的设计就是优先处理IO操作)
对于以下demo:
1 | console.log("sync"); |
setTimeout和setImmediate的问题
1 | setTimeout(() => { |
setTimeout在node中延迟时间最小取值是1ms,而不是0ms(最快1ms调用),所以这里谁先输出是不一定的,若要让“setImmediate”先执行,可以包在文件IO的回调里
1 | fs.readFile(_filename, () => { |