Comment comprendre le mécanisme de boucle d'événements de js

Prenez l'habitude d'écrire ensemble ! C'est le premier jour de ma participation au "Nuggets Daily New Plan · April Update Challenge", cliquez pour voir les détails de l'événement .

navigateur

Le navigateur fournit plusieurs threads. Le navigateur est également multi-processus. Par exemple, chaque page d'onglet du navigateur est un processus de rendu indépendant. Sous le thread de navigation, il y a : js engine thread, HTTP request thread, timing trigger Threads, time -Triggered threads, GUI threads, ces threads fournissent une base technique à js pour effectuer des tâches asynchrones dans le navigateur

événementiel

Le principe du déclenchement asynchrone des navigateurs est en fait un ensemble de mécanismes pilotés par le temps. Le déclenchement d'événements, la sélection de tâches et l'exécution de tâches sont tous pilotés par des événements. La conception des nodejs et des navigateurs est basée sur des boucles d'événements pilotées par des événements. Un ensemble de processus de gestion et d'exécution d'événements selon un modèle

Even Loop est la boucle d'événement

Tout d'abord, js est monothread et le navigateur est multithread. Il n'y a qu'un seul thread qui exécute le code js. Comment le thread du moteur js fourni par le navigateur empêche-t-il le blocage pendant l'exécution ? Le nœud du navigateur fournit un mécanisme de boucle d'événement pour empêcher Lorsque js s'exécute sur un seul thread, un mécanisme que les navigateurs et les nœuds ne bloqueront pas lors de l'exécution d'un seul thread js, et le mécanisme de boucle d'événement est le principe que nous utilisons souvent asynchrone

boucle d'événement dans le navigateur

Dans js, les tâches sont divisées en deux types, l'une est une tâche macro et l'autre est une tâche micro

Macrotâches et microtâches

1. Tâche macro :

  • setTimeout
  • setInterval
  • setImmediate (Le navigateur ne le supporte pas temporairement, seul IE10 le supporte, ce qui peut être vu en détail
  • requestAnimationFrame (navigateur exclusif)
  • E/S
  • Rendu de l'interface utilisateur (navigateur exclusif)

2. Micro-tâches :

  • Promesse asynchrone
  • Objet.observe
  • MutationObserver

Contraste : Caractéristiques des tâches macro : il existe des tâches asynchrones claires qui doivent être exécutées et rappelées, et d'autres threads asynchrones sont nécessaires pour prendre en charge les caractéristiques des tâches micro : aucune tâche asynchrone claire ne doit être exécutée, uniquement des rappels, aucun autre support de thread asynchrone n'est obligatoire

3. Pourquoi distinguer macrotâches et microtâches

  • pile d'appels

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

  • 任务队列

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

同步任务和异步任务

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. Pour le document qui doit être rendu, si la page défile, exécutez la scrollméthode
  7. Pour le document qui doit être rendu, exécutez le rappel d'animation d'image, qui est requestAninmationFramele rappel de
  8. Pour le document qui doit être rendu, exécutez IntersectionObserver (lorsqu'il écoute la partie visible de l'élément cible passant un ou plusieurs seuils, il exécutera la fonction de rappel spécifiée.)
  9. Pour les documents qui doivent être rendus, re-rendez et dessinez l'interface utilisateur
  10. Déterminez si la file d'attente de tâches et la file d'attente microTask sont vides, si c'est le cas, exécutez un Idlealgorithme de cycle d'inactivité pour déterminer s'il faut exécuter requestIdleCallbackla fonction de rappel

Pour le redimensionnement et le défilement, ce n'est pas à cette étape d'effectuer le défilement et le zoom

Je suppose que tu aimes

Origine juejin.im/post/7083460951491657758
conseillé
Classement