How to understand the event loop mechanism of js

Get into the habit of writing together! This is the first day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

browser

The browser provides multiple threads. The browser is also multi-process. For example, each tab page of the browser is an independent rendering process. Under the browsing thread, there are: js engine thread, HTTP request thread, timing trigger Threads, time-triggered threads, GUI threads, these threads provide a technical basis for js to complete asynchronous tasks in the browser

event driven

The principle behind the asynchronous triggering of browsers is actually a set of time-driven mechanisms. Event triggering, task selection, and task execution are all event-driven. The design of nodejs and browsers is based on event-driven event loops. A set of processes for managing and executing events in a pattern

Even Loop is the event loop

First of all, js is single-threaded, and the browser is multi-threaded. There is only one thread that executes js code. How does the js engine thread provided by the browser prevent blocking during execution? The browser's node provides an event loop mechanism to prevent When js is running on a single thread, a mechanism that browsers and nodes will not block when executing js single thread, and the event loop mechanism is the principle that we often use asynchronous

event loop in browser

In js, tasks are divided into two types, one is macro task and the other is micro task

Macrotasks and microtasks

1. Macro task:

  • setTimeout
  • setInterval
  • setImmediate (The browser does not support it temporarily, only IE10 supports it, which can be seen in detail
  • requestAnimationFrame (browser exclusive)
  • I / O
  • UI rendering (browser exclusive)

2. Micro tasks:

  • Promise async awit
  • Object.observe
  • MutationObserver

Comparison: Macro task characteristics: There are clear asynchronous tasks that need to be executed and called back, and other asynchronous threads are required to support Micro task characteristics: No clear asynchronous tasks need to be executed, only callbacks, no other asynchronous thread support is required

3. Why distinguish between macrotasks and microtasks

  • call stack

调用栈是一个后进先出的数据结构,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移除,直到栈内被清空

  • 任务队列

即队列,是一种先进先出的一种数据结构。

同步任务和异步任务

js单线程任务被分为同步任务和异步任务

同步任务会在调用栈中按照顺序等待主线程依次执行 异步任务会在异步任务有了结果之后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行

为什么区分?

image.png 异步 调用栈 消息队列 setTimeOut 微任务队列 执行完了之后就会evenloop循环

微任务比宏任务先执行 宏任务比微任务之间隔了一个DOM渲染

为什么要区分红任务和微任务 任务队列 先进先出 如果有优先级的任务引入微任务

node中的事件循环

将回调添加到轮询队列中以最终的执行

  • 循环 + 任务队列的流程
  • 微任务优先于宏任务

nodejs中其他常见的异步形式 :

  • 文件I/O读取-异步加载本地文件
  • setimmediate() -与settimeout设置0ms类似,在某些同步代码完成后立马执行
  • process.nextTick()- 在某些同步任务完成后立即执行
  • server close 等关闭回调

nodejs中的事件循环主要是在libuv库中执行的

nodejs的跨平台和事件循环机制都是预约libuv库的

libuv库怎么循环 :

  1. timers阶段,执行所有settimeout() setinterval()的回调
  2. pending callback 某些系统操作额的回调(比如tcp连接错误)
  3. idle prepare 仅是node内部使用
  4. poll 轮训等待新的链接和请求等事件,执行I/O回调等
  5. check setimmediate回调函数执行
  6. close callback 关闭回调执行,如socket.on('close',...)

实际上在node v10及以前的过程:

  1. 执行完一个阶段中的所有任务
  2. 执行nextTick队列里的内容
  3. 执行完微任务队的内容

但是在node v10以后和浏览器的行为一致了

timers

执行setTimeoutsetInterval中到期的callback,执行这两者需要设置一个毫秒数,理论上应该是时间一到就立即执行callback回调,但那是由于system的调度可能会延时,达不到预期的时间

官方文档的例子:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);


// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

复制代码

当进入时间循环时,他有一个空队列(fs.readFile()尚未完成),因此定时器将等待剩余毫秒数,当达到95ms时u,fs.readFile()完成读取文件并其完成需要10毫秒的回调被添加到轮询队列并执行

当回调结束时,队列中不再有回调,因此事件循环将看到已达到最快定时器的阈值,然后会回到timers阶段以执行定时器的回调

因此在此实例中,将看到正在调度的计时器与正在执行的回调之间的总延时将为105ms

poll

该poll阶段有两个主要功能

  • 执行I/O回调
  • 处理轮询队列中的事件(回到timer阶段执行回调)

当事件循环进入poll阶段并且在timer中没有可以执行定时器时,将发生以下两种情况之一:

  1. 如果poll队列不为空
  • 事件循环将遍历其同步执行他们的callback队列,直到队列为空,或者达到system-dependent(系统相关限制)

2.poll队列为空,则会发生以下两种情况

  • 如果setImmediate()回调需要执行,则会立即停止执行poll阶段并进入执行check阶段以执行回调
  • 如果没有setImmediate()回调需要执行,会等待回调被加入到队列中并立即执行回调,这里也有个超时的设置防止一直等待下去

设定了timer的话且poll队列为空,则会判断是否有timer超时,如果有的话回到timers阶段执行回调

check

此阶段允许人员在poll阶段完成后立即执行回调

setImmediate()的回调会被加入chenk队列中,从event loop 的阶段图可以知道,check阶段我的执行顺序在poll阶段后,如下例子

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')


复制代码

start=>end=>promise3=>timer1=>timer2=>promise1=>promise2

思考

  1. 每一轮Eventloop都会伴随着渲染吗
  2. requestAninmationFrame在哪个阶段执行,在渲染之前还是渲染之后?在microTask前还是后
  3. requestIdleCallback 在哪个阶段执行?如何去执行?在渲染前还是后?在 microTask 的前还是后?
  4. resizescroll这些事件都是如何去派发的

总结事件循环

定义 事件循环是为了协调事件,用户交互,脚本,渲染,网络任务

  1. 从宏任务队列中取出一个宏任务并执行
  2. 检查微任务队列,执行并且清空微任务队列,如果在微任务队列中又加入了新的微任务,也会在这一步一起执行
  3. 进入更新渲染阶段,判断是否需要渲染,这里有rendering opportunity 的概念,也就是说不一定每一轮eventloop都会对应一次浏览器渲染,要根据屏幕刷新率,页面性能,页面是否在后台运行来共同决定,通常来说这个渲染间隔是固定的(所以多个task很可能再一次渲染之间执行)
    1. 浏览器会尽可能的保持帧率稳定,例如页面性能无法维持60fps(每16.66ms渲染一次)的话,那么浏览器就会选择30fps的个更新速率,而不是偶尔丢帧
    2. 如果浏览器上下文不可见,那么页面会降低到4fps左右甚至更低
    3. 如果满足以下条件也会跳过渲染
      • 浏览器判断更新渲染不会带来视觉上的改变
      • 帧动画回调为空,可以通过requestAninmationFrame来请求帧动画
  4. 如果上述判断决定本来不需要渲染,那么下面几部也不继续运行
  5. 对于需要渲染的文档,如果窗口的大小发生了变化,执行监听的resize方法
  6. For the document that needs to be rendered, if the page scrolls, execute the scrollmethod
  7. For the document that needs to be rendered, execute the frame animation callback, which is requestAninmationFramethe callback of
  8. For the document that needs to be rendered, execute IntersectionObserver (when it listens to the visible part of the target element passing one or more thresholds, it will execute the specified callback function.)
  9. For documents that need to be rendered, re-render and draw the user interface
  10. Determine whether the task queue and microTask queue are empty, if so, perform an Idleidle cycle algorithm to determine whether to execute requestIdleCallbackthe callback function

For resize and scroll, it is not at this step to perform scrolling and zooming

Guess you like

Origin juejin.im/post/7083460951491657758