浅析JS的Event Loop机制

Posted 大前端学堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析JS的Event Loop机制相关的知识,希望对你有一定的参考价值。

根据JS的运行环境,Event Loop(事件循环)分为浏览器的Event Loop和NodeJS的Event Loop。


一、浏览器的Event Loop


浏览器端JS异步的实现宏观上是依靠浏览器的多线程,微观上是依靠的Event Loop,即事件循环。


先看一段示例代码:

console.log('1')setTimeout(function(){ console.log('2')},0)Promise.resolve().then(function(){ console.log('3')})console.log('4')
/*打印结果*///1//4//3//2


异步任务放到任务队列里。定时器先被放入任务队列中,为什么promise先执行呢?这里涉及异步任务的宏任务和微任务的概念。


浏览器环境下的 异步任务 分为 宏任务(macroTask) 和 微任务(microTask):


宏任务包括:

  • script代码块

  • setTimeout/setInterval

  • setImmediate

  • I/O

  • UI rendering


微任务包括:

  • Promise

  • Object.observe(监听对象变化的api)

  • MutationObserver(监听DOM树变化的api

  • postMessage


当满足执行条件时,宏任务(macroTask) 和 微任务(microTask) 会各自被放入对应的队列:宏任务队列(Macrotask Queue) 和 微任务队列(Microtask Queue) 中等待执行。


一个Event Loop 可以有一个或多个宏任务队列,但是只能有一个微任务队列。


浏览器的Event Loop 处理模型

浅析JS的Event Loop机制


基本执行顺序:

  1. 首先执行script标签里的同步代码,script被称为全局任务,也属于宏任务(macroTask)

  2. 当任务队列里一个宏任务执行完后,执行微任务队列里的所有微任务

  3. 微任务全部执行完,再取任务队列中的一个宏任务执行


注意:我们经常用的requestAnimationFrame既不在宏任务队列,也不在微任务队列。它是处在最终的渲染阶段。


了解到这里,我们分别看两个例子:

console.log("start");
setTimeout(() => { console.log("setTimeout"); new Promise(resolve => { console.log("promise inner1"); resolve(); }).then(() => { console.log("promise then1"); });}, 0);
new Promise(resolve => { console.log("promise inner2"); resolve();}).then(() => { console.log("promise then2");});
/*打印结果*///start//promise inner2//promise then2//setTimeout//promise inner1//promise then1


首先执行script这个宏任务里的同步代码,依次输出start、promise inner2,然后进入微任务队列执行所有的微任务,输出promise then2,再次执行下一个宏任务setTimeout,依次输出setTimeout、promise inner1,最后再次进入微任务队列执行所有的微任务,输出promise then1。


async function async1() { console.log("async1 start"); await async2(); console.log("async1 end");}async function async2() { return Promise.resolve().then(_ => { console.log("async2 promise"); });}
console.log("start");setTimeout(function() { console.log("setTimeout");}, 0);async1();new Promise(function(resolve) { console.log("promise1"); resolve();}).then(function() { console.log("promise2");});
/*打印结果*///start//async1 start//promise1//async2 promise//promise2//async1 end//setTimeout


首先执行script这个宏任务里的同步代码,依次输出start、async1 start、promise1,然后进入微任务队列执行所有的微任务,依次输出async2 promise、promise2、async1 end,再次执行下一个宏任务setTimeout,输出setTimeout。


二、NodeJS的Event Loop


浏览器的Event loop是在html5中定义的规范,而NodeJS中则由libuv库实现。libuv库流程大体分为6个阶段:


  1. timers: 执行定时器(setTimeout/setInterval)的回调

  2. pending callbackss: 系统操作的回调

  3. idle,prepare: 内部使用

  4. poll: 等待新的I/0事件

  5. check: 执行setImmediate回调

  6. close callbacks: 内部使用


每一个阶段都有一个callbacks的先进先出的队列需要执行。当event loop运行到一个指定阶段时,该阶段的fifo队列将会被执行,当队列callback执行完或者执行的callbacks数量超过该阶段的上限时,event loop会转入下一个阶段。


NodeJS的Event Loop 处理模型

浅析JS的Event Loop机制


Poll阶段的两个主要功能:

  1. 计算应该被block多久

  2. 处理poll队列的事件


注意: process.nextTick()是一个异步的node API,但不属于event loop的阶段。调用这个方法,可以使event loop暂停,优先执行这个方法的回调函数。


了解到这里,我们分别看两个例子:

const fs = require('fs');
function someAsyncOperation(callback) { fs.readFile(__dirname, callback);}
const timeoutScheduled = Date.now();
setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`);}, 100);
someAsyncOperation(() => { const startCallback = Date.now(); while (Date.now() - startCallback < 200) { // 200ms内执行某些行为 }});
/* 执行结果 *///204ms have passed since I was scheduled


执行someAsyncOperation方法后,花4ms读取文件,然后进入poll队列执行回调函数,200ms内执行某些行为,200ms后退出poll队列,检查是否有定时器,如果有且已到时间则执行这个定时器的回调函数。并不是定时器设置多长时间后执行就可以执行,需要等待poll队列里的回调函数都执行完,才能执行这个定时器。


const fs = require("fs");fs.readFile(__filename, _ => { setTimeout(_ => { console.log("setTimeout"); }, 0); setImmediate(_ => { console.log("setImmediate"); process.nextTick(_ => { console.log("nextTick2"); }); }); process.nextTick(_ => { console.log("nextTick1"); });});
/* 执行结果 *///nextTick1//setImmediate//nextTick2//setTimeout


读取文件后进入poll队列,有nextTick就暂停event loop,优先执行nextTick的回调函数,打印nextTick1,然后进入check阶段,执行setImmediate,打印setImmediate,有nextTick就暂停event loop,优先执行nextTick的回调函数,打印nextTick2,然后进入定时器阶段,执行到时的定时器的回调函数,打印setTimeout。



本文为原创内容,若转载请注明出处,转发感激不尽。

以上是关于浅析JS的Event Loop机制的主要内容,如果未能解决你的问题,请参考以下文章

js事件循环机制(Event Loop)

小心得浅析Nodejs Event Loop

前端基础 | JS异步执行机制——事件循环(Event Loop)

彻底弄懂 JavaScript 执行机制(Event Loop)

JS引擎的执行机制event loop

js执行机制:event loop(多图理解)