手机版

详细解释JavaScript事件循环机制

时间:2021-08-30 来源:互联网 编辑:宝哥软件园 浏览:

众所周知,JavaScript是一种单线程语言。虽然Web-Worker是在html5中提出的,但它并没有改变JavaScript是单线程的核心。看看HTML规范中的这段话:

为了协调事件、用户交互、脚本、渲染、网络等,用户代理必须使用本节中描述的事件循环。有两种事件循环3360,一种用于浏览上下文,另一种用于工作人员。

为了协调事件、用户交互、脚本、UI呈现和网络处理,用户引擎必须使用事件循环。事件循环包括两类:一类基于浏览上下文,另一类基于Worker,它们独立运行。下面的文章通过一个例子重点介绍了基于浏览上下文的事件循环机制。

请看下面的JavaScript代码:

console.log('脚本开始');setTimeout(function(){ console . log(' setTimeout ');}, 0);Promise.resolve()。然后(function(){ console . log(' promise 1 ');}).然后(function(){ console . log(' promise 2 ');});console.log('脚本结束');先猜测这段代码的输出顺序是什么,然后在浏览器控制台中输入,看看实际的输出顺序是否和你猜测的顺序一致。如果是一致的,说明你对JavaScript的事件循环机制还是有一定了解的。继续往下看巩固自己的知识;而如果实际的输出顺序与你的猜测不一致,本文接下来的部分将回答你的问题。

任务排队

所有任务可以分为同步任务和异步任务。同步任务,顾名思义就是立即执行的任务,同步任务一般在主线程中直接执行;异步任务是异步任务,如ajax网络请求、setTimeout定时器函数等。全部属于异步任务,异步任务通过事件队列机制进行协调。具体来说,可以用下图来大致解释一下:

同步和异步任务进入不同的执行环境,同步进入主线程,即主执行栈,异步进入事件队列。如果执行后主线程中的任务为空,则从事件队列中读取相应的任务,并推送到主线程中执行。上述过程的重复就是我们所说的事件循环。

在事件循环中,每个循环操作都称为tick。通过阅读说明书可以看出,每个tick的任务处理模型比较复杂,其关键步骤可以概括为:

选择最早进入队列的任务,如果有,执行(一次)检查是否有微任务,如果有,继续执行,直到微任务队列被清除,渲染主线程被更新,重复上述步骤

您可以用一张图片来说明以下过程:

相信在座有些人可能会想问,什么是微题?根据规范,任务分为两类,即宏任务和微任务,每个宏任务完成后,所有微任务都要清空。这里的宏观任务也是我们经常说的任务,有些文章没有区分,后面文章提到的任务都被认为是宏观任务。

(宏)任务主要包括:脚本(全代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js环境)

微任务主要包括:承诺、变更、服务器、进程

setTimeout/Promise等API是任务源,它们指定的具体执行任务进入任务队列。来自不同任务源的任务将进入不同的任务队列。其中setTimeout和setInterval是同源的。

分析示例代码

千言万语不如举例清楚。接下来,我们可以按照规范一步一步地分析上面的示例,先粘贴示例代码(免得你翻出来)。

console.log('脚本开始');setTimeout(function(){ console . log(' setTimeout ');}, 0);Promise.resolve()。然后(function(){ console . log(' promise 1 ');}).然后(function(){ console . log(' promise 2 ');});console.log('脚本结束');作为第一个宏任务,整个脚本进入主线程,遇到console.log,并输出脚本start

遇到setTimeout,将其回调函数分配给宏任务事件队列,遇到Promise,将其then函数分配给微任务事件队列,记录为then1,然后分配给微任务事件队列,记录为then2 met console.log,输出脚本到此结束。事件队列中有三个任务,如下表所示:

宏任务微任务setTimeout然后1-then2执行微任务,先执行then1并输出承诺1,再执行then2并输出承诺2,从而清除所有微任务执行setTimeout任务并在此输出setTimeout。输出顺序是:脚本开始,脚本结束,承诺1,承诺2,settimeout那么,你猜对了吗?

看看你有没有

让我们有另一个话题,做一个练习:

console.log('脚本开始');setTimeout(function(){ console . log(' time out 1 '));}, 10);新承诺(resolve={ console . log(' Promise 1 ');resolve();setTimeout(()=console . log(' time out 2 '),10);}).然后(function(){ console . log(' then 1 ')})console . log(' script end ');这个话题有点复杂,我们再来分析一下:

首先,事件周期从宏任务队列开始。最初,宏任务队列中只有一个脚本任务。当遇到任务源时,任务将首先被分发到相应的任务队列。因此,类似于上面的例子,首先遇到console.log和输出脚本start;然后下去,遇到setTimeout任务源,分配到任务队列,记录为timeout1;然后当你遇到promise时,新promise中的代码会立即执行,输出promise1,然后执行resolve,当你遇到setTimeout时,分发到任务队列并记录为timemout2,然后分发到微任务队列并记录为then 1;然后遇到console.log代码,直接输出脚本end,然后检查微任务队列,找到一个then1微任务,执行,输出then1,再次检查微任务队列,如果发现是空的,开始检查宏任务队列,执行timeout1,输出time out 1;然后执行timeout2,输出timeout2至此,所有队列都已清空,执行完成。输出顺序是脚本开始,承诺1,脚本结束,然后是1,超时1,超时2

使用流程图看得更清楚:

摘要

有一个小提示:从规范的角度来看,microtask优先于任务执行,所以如果有什么逻辑需要先执行,就会在microtask队列之前执行。

最后,记住JavaScript是单线程语言,异步操作都放在事件循环队列中,等待主执行栈执行,没有专门的异步执行线程。

版权声明:详细解释JavaScript事件循环机制是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。