学习链接
浏览器与Node的事件循环(Event Loop)有何区别?
Tasks, microtasks, queues and schedules(动画👍)
事件循环
同步任务和异步任务
JavaScript 的任务分为同步任务(synchronous)和异步任务(asynchronous)两种:
(“任务”不一定单指某条语句的执行)
- 同步任务是那些没有被引擎挂起、在主线程上排队执行的任务
- 只有前一个任务执行完毕,才能执行后一个任务
- 异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务
- 只有引擎认为某个异步任务可以执行了,该任务才会进入主线程执行
- 排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应
再具体一些:
- 同步任务会被放到调用栈(call stack)中排队,主线程会去执行调用栈中的任务
- 异步任务会被放到任务队列(task queue)排队等待
- 等到调用栈中的同步任务全部执行完后,就会去任务队列中寻找满足条件的异步任务
- 将满足条件的异步任务推入调用栈中,此时变为同步任务,主线程会执行调用栈中的任务
- 执行完后,继续去任务队列中寻找下一个符合要求的异步任务
引擎不断地检查调用栈是否为空,以及确定把哪个异步任务推入调用栈的这个过程就是事件循环(Event Loop)。
浏览器中的事件循环
- 浏览器至少有一个事件循环
- 一个事件循环有一个或多个宏任务队列(macrotask queue)
- 一个事件循环只有一个微任务队列(microtask queue)
- 一个任务可能会被推入宏任务队列或微任务队列
- 当一个任务被推入某队列 (micro/macro) 时,则认为该任务的准备工作都已完成,可被立即执行
事件循环
当调用栈为空时,执行步骤👇
- 在宏任务队列中选取排在最前面的一个任务,推入调用栈中执行,执行完后移出队列
- 若宏任务队列一开始就为空,则直接👉3
- 【清空】微任务队列
- 在微任务队列中选取排在最前面的一个任务,推入调用栈中执行,执行完后移出队列
- 若微任务队列为空,则👉4
- 继续选择下一个排在最前面的微任务👉3.1
- 直到微任务队列被清空
- 执行渲染操作,更新界面(未必每次都更新,优化:累计更新)
- 检查是否存在 Web worker 任务,如果有,则对其进行处理
- 继续选择下一个排在最前面的宏任务👉1
- 宏任务队列被清空
当调用栈为空时,
将最前面的一个宏任务推入调用栈中执行,
然后再将一队微任务逐个推入调用栈中执行(即清空微任务队列),
接着,进行视图的渲染(未必每轮都执行),
然后进入下一轮事件循环,开始处理下一个宏任务,
直到宏任务队列和微任务队列都被清空。
每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。
宏任务与微任务
- 常见的宏任务:setTimeout、setInterval、script (整体代码)、事件回调、XHR 回调、I/O 操作、UI 渲染等。(setImmediate——node.js中的实现)(事件的触发?)
- 常见的微任务:Promise 回调、MutationObserver (html5新特性) 等。(process.nextTick——node.js中的实现)
注意:每个微任务都可产生新的微任务(如此递归可能导致卡死)
安排(schedule)一个新的 宏任务:
- 使用零延迟的
setTimeout(f)
。
它可被用于将繁重的计算任务拆分成多个部分,以使浏览器能够对用户事件作出反应,并在任务的各部分之间显示任务进度。
此外,也被用于在事件处理程序中,将一个行为(action)安排(schedule)在事件被完全处理(冒泡完成)后。
安排一个新的 微任务:
- 使用
queueMicrotask(f)
。 - promise 处理程序也会通过微任务队列。
在微任务之间没有 UI 或网络事件的处理:它们一个立即接一个地执行。
所以,我们可以使用 queueMicrotask
来在保持环境状态一致的情况下,异步地执行一个函数。
setTimeout 和 setInterval
setTimeout
和 setInterval
的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行指定的代码;如果不到,就继续等待。
这意味着,setTimeout
和 setInterval
指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才可能开始执行。
由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout
和 setInterval
指定的任务,一定会按照预定时间执行。
Node 中的事件循环
https://github.com/ljianshu/Blog/issues/54
http://lynnelv.github.io/js-event-loop-nodejs
- Node.js 的事件循环分为6个阶段
- 浏览器和 Node 环境下,
microtask
任务队列的执行时机不同- Node.js中,
microtask
在事件循环的各个阶段之间执行 - 浏览器端,
microtask
在事件循环的macrotask
执行完之后执行
- Node.js中,
Web Worker
https://wangdoc.com/javascript/bom/webworker.html
JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务可以交由 Worker 线程执行,主线程(通常负责 UI 交互)能够保持流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。