当前位置: 首页 > Web前端 > JavaScript

Eventloop

时间:2023-03-27 10:51:24 JavaScript

EventloopJavascript基于事件循环以不同的方式运行。Microtaskqueue和taskqueue让很多人困惑为什么代码会这样运行。让我们看看这里的事件循环。Eventloop的执行过程是ECMA-262作为Javascript背后的标准。ECMA-262中没有定义EventLopp,也没有microtaskqueue和taskqueue。(ECMA-262也没有定义setTimeout等定时函数,也没有I/O。)它定义了Job与Job队列,并且有一个顶层的[RunJobs]操作:执行?InitializeHostDefinedRealm()。以依赖于实现的方式,为零个或多个ECMAScript脚本和/或ECMAScript模块获取ECMAScript源文本(参见第10条)和任何关联的主机定义值。对于每个这样的sourceText和hostDefined,doIfsourceText是脚本的源代码,然后ni。执行EnqueueJob("ScriptJobs",ScriptEvaluationJob,?sourceText,hostDefined?).ElsesourceText是模块的源代码,ii。PerformEnqueueJob("ScriptJobs",TopLevelModuleEvaluationJob,?sourceText,hostDefined?).Repeat,Suspendrunningexecutioncontextandremoveitfromtheexecutioncontextstack.Assert:Theexecutioncontextstackisnowempty.让nextQueue是一个非空的Job以实现定义的方式选择队列。如果所有JobQueues都为空,则结果由实现定义。令nextPending为nextQueue最前面的PendingJob记录。删除e从nextQueue中记录。让newContext成为一个新的执行上下文。将newContext的Function设置为null。将newContext的Realm设置为nextPending。[[Realm]]。将newContext的ScriptOrModule设置为nextPending。[[ScriptOrModule]]。将newContext推送到执行上下文堆;newContext现在是运行的执行上下文。使用nextPending执行任何实现或主机环境定义的作业初始化。让结果为执行nextPending.[[Job]]命名的抽象操作的结果使用nextPending.[[Arguments]]的元素作为它的论据。如果result突然完成,则执行HostReportErrors(?result.[[Value]]?)。这里首先根据脚本的类型,将ScriptEvaluationJob或TopLevelModuleEvaluationJob添加到ScriptJob队列中,然后开始处理每个非空的Job队列。在同一个作业队列中,任务严格先进先出。但是,选择哪一个是一个实施决定。但是ECMA-262只定义了两个Job队列,一个是上面的顶层ScriptJob,只用来放(唯一的)顶层任务;另一个是PromiseJob,用于Promise。这两个作业队列不会同时为空,所以执行顺序其实是确定的。EnqueuJob用于将Job队列加入到Job中。HTML中HTML使用的Javascript并没有使用上面提到的RunJobs和Jobqueue,而是定义了自己的EventLoop、taskqueue和microtaskqueue。HTML的EventLoop执行如下操作:让taskQueue成为事件循环的任务队列之一,以用户代理定义的方式选择,具有所选任务队列必须包含至少一个可运行任务的约束。如果没有这样的任务队列,则跳转到下面的微任务步骤。让oldestTask成为taskQueue中第一个可运行的任务,并将其从taskQueue中移除。报告用户代理不执行此循环的持续时间,方法是执行以下步骤:将事件循环开始设置为当前高分辨率时间。如果设置了事件循环结束,则让顶级浏览上下文为与事件循环关联的所有文档对象的所有顶级浏览上下文的集合。报告长任务,传入事件循环结束、事件循环开始和顶级浏览上下文。将事件循环当前运行的任务设置为oldestTask。执行oldestTask的步骤。将事件循环当前运行的任务设置回null。移除oldestTaskfromitstaskqueue.Microtasks:执行微任务检查点。让现在成为当前的高分辨率时间。[HRT]通过执行以下步骤来报告任务的持续时间:...以下被省略注意微任务队列不是任务队列,任务队列不是队列,因为它不是先进先出。从第一步可以看出,从任务队列中取出的任务不一定是第一个进入任务队列的任务。在事件循环中,在完成一个任务队列任务后,会执行一个微任务检查点:如果事件循环执行一个微任务检查点为真,则返回。将事件循环执行一个微任务检查点设置为真。而事件循环的微任务队列为不为空:让oldestMicrotask为从事件循环的微任务队列中出队的结果。将事件循环当前运行的任务设置为oldestMicrotask。运行oldestMicrotask。将事件循环当前运行的任务设置回null。对于循环为每个环境响应设置对象此事件循环,通知有关该环境设置对象的拒绝承诺。清理索引数据库事务。将事件循环的执行微任务检查点设置为false。这里,所有微任务队列都会按照先进先出的顺序执行任务(包括在此过程中有新任务进入微任务队列),直到微任务队列为空。所以执行过程就是执行一个任务队列任务,然后执行microtask队列中的所有任务,然后进入EventLoop的下一个循环,再执行一个任务队列任务。ECMA-262中EnqueuJob添加的作业都进入HMTL中的微任务队列)。如果只有Promise产生的microtasks,上面的执行过程和RunJobs基本一样。所以下面的讨论将基于HTML的EventLoop和microtask/task的定义。TaskvsMicrotask那么,microtask和task之间有什么区别呢?这里介绍一部分,应该可以解决网上很多“为什么输出是这样”的问题。ECMA-262中所有与MicrotaskPromise相关的工作。包括:then和catch的回调如果在调用then或catch时Promise已经结算(状态已经确定),那么相应的回调函数将直接加入微任务队列,参见PerformPromiseThen。否则返回被记录,待Promise结算完毕,通过TriggerPromiseActions加入微任务队列。await由Promise实现,每次await都会通过Promise.then执行await结束后的操作(即使await的对象不是Promise)。(参见Await)当一个Promise(P1)resolve另一个Promise(P2)时,会自动生成对P2.then的调用。该调用将被添加到微任务队列中。见PromiseResolveFunctionsP2.then执行后,P2.then的回调会成为另一个微任务P2.then的回调,会解析或拒绝P1MutationObserversTaskSetTimeout和SetInterval的回调。即使时间设置为0。看timer的初始化步骤,callback会在延时完成后加入到任务队列中。