微信公众号:[前端一锅煮]
一点技术、一点思考。
问题或建议,请公众号留言。
抛在前面的问题:
js 是单线程的如何做到异步?
事件循环的过程是怎样的?
macrotask 和 microtask 是什么,它们有何区别?
node.js 的事件循环是怎样的,和浏览器的事件循环有何区别?
进程和线程
浏览器是多进程的,具体包含的进程有:
- Browser 进程:浏览器的主进程(负责协调、主控),只有一个;
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建;
- GPU 进程:最多一个,用于 3D 绘制;
- 浏览器渲染进程(内核):默认每个 tab 页面一个进程,互不影响,控制页面渲染,脚本执行,事件处理等(有时候会优化,如多个空白 tab 会合并成一个进程)。
其中浏览器渲染进程就是前端页面主要用到的进程,包含的线程有:
- GUI 渲染线程(负责渲染页面,解析 HTML,CSS 构成 DOM 树,和 JS 引擎互斥)
- JS 引擎线程
- 事件触发线程
- 定时器触发线程
- Http 请求线程等主要线程
关于执行中的线程:
主线程:也就是 js 引擎执行的线程,此线程只有一个,页面渲染、函数处理都在这个主线程上执行。
工作线程:也称幕后线程,这个线程可能存在于浏览器或 js 引擎内,与主线程是分开的,处理文件读取、网络请求等异步事件。
事件循环
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行。而异步任务,就是异步执行的任务,比如 ajax 网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是我们说的 Event Loop (事件循环)。
在事件循环中,每进行一次循环操作称为 tick,其关键的步骤可以总结如下:
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,读取微任务列表,有则依次执行,直到全部执行完
- 读取宏任务列表,有则依次执行,直到全部执行完
- 执行浏览器 UI 线程的渲染工作
- 检查是否有 Web Worker 任务,有则执行
- 执行完本轮的宏任务,回到第 2 步,继续依此循环,直到宏任务和微任务队列都为空
宏任务与微任务
JS 引擎把所有任务分成两类,一类叫宏任务(macrotask),一类叫微任务(microtask)。
宏任务主要包含:
- js(整体代码)
- I/O、UI 渲染
- MessageChannel、postMessage
- setImmediate(Node.js 环境)
- setTimeout、setInterval
- requestAnimationFrame 属于 GUI 引擎,发生在渲染过程的重绘重排部分,在 UI 渲染之前执行
微任务主要包含:
- process.nextTick(Node.js 环境)
- MutaionObserver(浏览器环境)
- Promise
先执行完微任务再执行宏任务。
Node.js 的事件循环
事件循环是 Node.js 处理非阻塞 I/O 操作的机制。目前大多数内核都是多线程的,它们可在后台处理多种操作。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到轮询队列中等待时机执行。
当 Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本,它可能会调用一些异步的 API、调度定时器,或者调用 process.nextTick(),然后开始处理事件循环。
以下是 Node.js 事件循环顺序:
- timers:执行到期的 setTimeout、setInterval 回调。
- pending callbacks:挂起的回调函数,执行延迟到下一个循环迭代的 I/O 回调。对某些系统操作(如TCP错误类型)执行回调。
- idle, prepare:空闲 准备,node 系统内部使用。
- poll:检索新的 I/O 事件。执行 I/O(例如文件、网络)的回调,除了 close,定时器和 setImmediate 以外的所有回调。其余情况 node 将在适当的时候在此阻塞。
- check:执行 setImmediate 回调。
- close callbacks:执行 close 事件回调,如 socket.on('close', ...)、http close等。
上面是宏任务的执行顺序,Node.js 也是先执行微任务再执行宏任务。微任务主要有 process.nextTick 和 promise,其中 process.nextTick 先执行。
最后,JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。