事件循环机制 Event-Loop及其延伸
amandakelake opened this issue · 5 comments
#Front-End/JS/基础
参考资料
从一道题说JavaScript的事件循环
这一次,彻底弄懂 JavaScript 执行机制 - 掘金 这篇真的讲的特别通俗易懂
【朴灵评注】JavaScript 运行机制详解:再谈Event Loop - CSDN博客
event loop英文版 有动画
Node 定时器详解-阮一峰 - 后端 - 掘金
从event loop规范探究javaScript异步及浏览器更新渲染时机 · Issue #5 · aooy/blog · GitHub 本篇文章用了实验测试
JS是单线程的,这个线程中拥有唯一的一个事件循环,一切javascript版的"多线程"都是用单线程模拟出来的
事件循环是js实现异步的一种方法,也是js的执行机制
事件循环的顺序
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
微任务与宏任务
宏任务
- script(整体代码)
setTimeout
、setInterval
,这二者同源,旗下微任务会进入相同的任务队列- I/O
- UI rendering
setImmediate
(Node.js 环境)
微任务
Promise
MutaionObserver
(HTML5 新特性)process.nextTick(Node.js 环境)
题外补充知识
JavaScript执行环境(Runtime)和执行引擎(Engine)的关系
JavaScript引擎的内部运行机制跟Event loop没有半毛钱的关系
引擎指的是虚拟机,对于Node来说是V8、对Chrome来说是V8、对Safari来说JavaScript Core,对Firefox来说是SpiderMonkey
JavaScript的执行环境就是上面所说的浏览器、node、Ringo
为什么JS是单线程的
与用途有关,JavaScript的主要用途是与用户互动,以及操作DOM
假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
任务队列
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
【JavaScript运行环境的运行机制,不是JavaScript的运行机制。】
【上面这段初步地在说event loop。但是异步跟event loop其实没有关系。准确的讲,event loop是实现异步的一种机制】
【一般而言,操作分为:发出调用和得到结果两步。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。同步就是调用之后一直等待,直到返回结果。异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务)。】
【上面提到的一系列的手段其实就是实现异步的方法,其中就包括event loop。以及轮询、事件等。】
【所谓轮询:就是你在收银台付钱之后,坐到位置上不停的问服务员你的菜做好了没。】
【所谓(事件):就是你在收银台付钱之后,你不用不停的问,饭菜做好了服务员会自己告诉你。】
nodejs的event loop分为6个阶段
不要混淆nodejs和浏览器中的event loop
每一轮的事件循环,分成六个阶段。这些阶段会依次执行。
timers
setTimeout
、setInterval
I/O callbacks
除了以下操作的回调函数,其他的回调函数都在这个阶段执行。
- setTimeout()和setInterval()的回调函数
- setImmediate()的回调函数
- 用于关闭请求的回调函数,比如socket.on('close', ...)
idle, prepare
该阶段只供 libuv 内部调用,这里可以忽略
poll
这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。
这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。
check
setImmediate
close callbacks
该阶段执行关闭请求的回调函数,比如socket.on('close', ...)。
setTimeout和setImmediate的先后顺序
由于setTimeout在 timers 阶段执行,而setImmediate在 check 阶段执行。所以,setTimeout会早于setImmediate完成。
实际执行的时候,结果却是不确定
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数
但是,这个代码却setImmediate优先于setTimeout执行
const fs = require('fs');
fs.readFile('test.js', () => {
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
});
上面代码会先进入 I/O callbacks 阶段,然后是 check 阶段,最后才是 timers 阶段。因此,setImmediate才会早于setTimeout执行。
浏览器层面的event loop
有两种event loops,一种在浏览器上下文,一种在workers中。
浏览器上下文是一个将 Document 对象呈现给用户的环境。在一个 Web 浏览器内,一个标签页或窗口常包含一个浏览上下文,如一个 iframe 或一个 frameset 内的若干 frame。
每个线程都有自己的event loop。
浏览器可以有多个event loop,browsing contexts和web workers就是相互独立的。
所有同源的browsing contexts可以共用event loop,这样它们之间就可以相互通信。
event loop中的Update the rendering(更新渲染)
从event loop规范探究javaScript异步及浏览器更新渲染时机 · Issue #5 · aooy/blog · GitHub 这里用了实验测试
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树, 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
Note: 可以看到渲染树的一个重要组成部分是CSSOM树,绘制会等待css样式全部加载完成才进行,所以css样式加载的快慢是首屏呈现快慢的关键点。
结论
在一轮event loop中多次修改同一dom,只有最后一次会进行绘制。
渲染更新(Update the rendering)会在event loop中的tasks和microtasks完成后进行,但并不是每轮event loop都会更新渲染,这取决于是否修改了dom和浏览器觉得是否有必要在此时立即将新状态呈现给用户。如果在一帧的时间内(时间并不确定,因为浏览器每秒的帧数总在波动,16.7ms只是估算并不准确)修改了多处dom,浏览器可能将变动积攒起来,只进行一次绘制,这是合理的。
如果希望在每轮event loop都即时呈现变动,可以使用requestAnimationFrame。
具体浏览器的工作原理参考这里新式网络浏览器幕后揭秘
这是篇汇总啊
事件循环不是js的机制,而应该是js运行环境的机制。js的执行引擎里只有堆和栈而已,剩下的任务队列,事件循环都属于执行环境。
@xusanduo08
嗯,对的,感谢指出
赞
“所有同源的browsing contexts可以共用event loop,这样它们之间就可以相互通信。”
这句话是有前提的吧,否则效果太吓人了