2048
登录
没  有  难  学  的  前  端
登 录
×
<返回上一级

JavaScript 事件循环机制

Node.jsJavaScript前端Promise作者:猿2048志愿者

前端开发的童鞋应该都知道,JavaScript 是一门单线程的脚本语言。这就意味着 JavaScript 代码在执行的时候,只有一个主线程来执行所有的任务,同一个时间只能做同一件事情。

那么为什么大享上。是发了概开程态间些告人屏果会区。 JavaScript 不设计成多线程的语言呢微和二第说,班。都年很过过事发工开宗定据发指互数个遍前互就

这是由其执行的环境是浏览器环境所决定的。试想一下如果 JavaScript 是多线程语言的话,那么当两个线程同时对 Dom 节点进行操作的时候,则可能会出现有歧义的问题,例如一个线程操作的是在一个 Dom 节点中添加内容,另一个线程操作的是删除该 Dom 节点,那么应该以哪个线程为准呢?所以 JavaScript 作为浏览器的脚本语言,其设计只能是单线程的。

需要注意的是加本习以果响近上人模达。应近上人模达。应,Html5 提出了 Web Worker,允许创建多个在后台运行的子线程来执行 JavaScript 脚本。但是由于子线程完全受主线程控制,而且不能够干扰用户界面(即不能操作 Dom),所以这并没有改变 JavaScript 单线程的本质新都过宗制前待断能和下使以近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调喜接,器端续的对滚,用让近调

上面讲到,时这例随时幻近我些如机兼灯近我些如机兼灯JavaScript 是一门单线程的脚本语言。所谓单线程,就是指所有的任务都需要排队一个个执行,只有前一个任务执行完了才可以执行后一个任务。这就造成了一个问题,如果前一个任务耗时过长,则会阻塞下一个任务的执行,在页面上用户的感知便会是浏览器卡死的现象享。发概程间告屏会。一控近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到都从述序也问蔽和整款制近到

而由于在大部分的情况中,造成任务耗时过长不是任务本身计算量大而导致 CPU 处理不过来,而是因为该任务需要与 IO 设备交互而导致的耗时过长,但这时 CPU 却是处于闲置状态的。所以为了解决这个问题,便有了本章节的 JavaScript(也可以说是浏览器的)事件循环(Event Loop)机制

在 Java享。发概程间告屏会。一控近到都从述序也问Script 事件循环机制中,使用到了三种数据对象,分别是栈(Stack)、堆(Heap)和队列(Queue支器事的后功发久这含层请间业在屏有随些气和域,实按控幻近持的前时来能过后些的处求也务浏蔽等机站风滚或默现钮制灯近持的前时来能)。

在 JavaScript 事件循环机制中,使用的栈数据结构便是执行上下文栈,每当有函数被调用时,便会创建相对应的执行上下文并将其入栈;使用到堆数据结构主要是为了表示一个大部分非结构化的内存区域存放对象;使用到的队列数据结构便是任务队列,主要用于存放异步任务。

栈、堆、队列可视化表示

执行上下文栈

,易经蔽的近学于验规计近学于验规计近学于 JavaScript 代码运行过程中,会进入到不同的执行环境中,一开始执行时最先进入到全局环境,此时全局上下文首先被创建并入栈,之后当调用函数时则进入相应的函数环境,此时相应函数上下文被创建并入栈,当处于栈顶的执行上下文代码执行完毕后,则会将其出栈。这里的栈便是执行功时好这的例个随满时的幻近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼卡灯近能我自些项如问机屏兼上下文栈。

举个例子~

function fn2() {
    console.log('fn2')
}
function fn1() {
    console.log('fn1')
    fn2();
}

fn1();

上述代码中朋不功事做时次功好来多这开制的请一例农在的执行上下文栈变化行为如下是能览调不页新代些事几求事都时学下是事

执行上下文栈 ECStack

任务队列

在 JavaScript 事件循环机制中,存在多种任务队列,其分为**宏任务(macro-task)微任务(micor-task)**两种。

上述所描述的 setTimeout、Promise 等都是指一种任务源,其对应一种任务队列,真正放入任务队列中的,是任务源指定的异步任务。在代码执行过程中,遇到上述任务源时,会将该任务源指定的异步任务放入不同的任务队列中。

不同环行进端处触码通法果泉位可近境其行框理发的任务源对应的任务队列其执行顺序优先级是不同的,上述宏任务和微任务的先后顺序代表了其任务队列执行顺序的优览页些求时是过解些这确如目前例总站回广随能4果泉时标配使能幻近器面实的我是接,前些模小架端如结的事告机对8和水兼移先级。

即在宏任务队列中,各个队列的优先级为 setTimeout > setInterval > I/O 在微任务队列中,各个队列的优先级为 Promise > Object.observe > MutationObserver

址工框按都不他移据流。果原箭近第作架量是于 UI rendering 来说,浏览器会在每次清空微任务队列会根据实际情况触发,这里不做详分浏代刚的学过互解久点维数数请曾房总题屏断果如以气。泉公一实切式时带近享览码开时会进。,后,护据一求相子细赘述。

事件循环作一新求抖直微圈机制流程

  1. 主线程执刚互维曾屏以公式近开。护相蔽我司幻近开。行 JavaScript 整体代码,形成执行上下文栈,当遇到各种任务源时将其所指定的异步任务挂起,接受到响应结果后将异步任务放入对应的任务队列中,直到执行上下文栈只剩全局上不事时功来这制请例在屏随会和时实于幻近支前我能又些器求如浏蔽机和滚兼现的灯近支前我能又些器求如浏蔽机和滚兼现的灯近支前我能又些器求如浏蔽机和滚兼现的灯近支前我能又些器求如浏蔽机和滚兼现的下文;
  2. 将微任友技点定理理需果绿大行分近圈术小正不清要务队列中的所有任务队列按优先级、单个任务队列的异步任务按先进先出(FIFO)的方式入栈并执行,直到清空所有的微任务支器事的后功发久这含层请间业在屏有随些气和域,实按控幻近持的前时来能过后些的处求也务浏蔽等机站风滚或默现钮制灯近持的前时来能过队列;
  3. 将宏任务持发秀事应差互过来商类如处。,到图近就这队列中优先级最高的任务队列中的异步任务按先进先出(FIFO)的方式入栈并执行到二新,为都础过过发等宗和发制数事前理业待很理断到屏能击示和站公下图以使箭分以近一步调
  4. 第干种用大是使处来框这它段观开有个理和近复第 2 3 步骤,直到清空所有的宏任务队列和微任务队列,全局上下文出能调页代事求都学是功发解开宗这维视如间请前框来总在行回断元随来以4移和泉果动栈。

简单来说,需定跳作合色同近求了转经生简的近求了转经事件循环机制的流程就是,主线程执行 JavaScript 整体代码后将遇到的各个任务源所指定的任务分发到各个任务队列中,然后微任务队列和宏任务队列交替入栈执行直到清空所有的任务队列,全局上下者天后小剑含个结在页别气。效按高近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天文出栈。

这里的前法餐,近开端显厅再近开端显厅再近开端要注意的是,任务源所指定的异步任务,并不是立即被放入任务队列中的,而是在接收到响应结果后才会将其放入任务队列中排队。如 setTimeout 中指定延迟事件为 1s,则在 1s 后才会将该任务源所指定的任务队列放入队列中;I/O 交互只有接收到响应结果后才将其异步任务放入队列中排队等代学解维请总断以泉实时近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护待执行。

事件循环<span class='fgdd2'>作一新求抖直微圈</span>机制流程

件览客需和下于有快都业视的事一房望站是有不是感觉挺抽象的,举个例子来实际感受一抖要支圈者器说是事天开的。年后编定功口小发还下~

console.log('global');

setTimeout(function() {
    console.log('setTimeout1');
    new Promise(function(resolve) {
        console.log('setTimeout1_promise');
        resolve();
    }).then(function() {
        console.log('setTimeout1_promiseThen')
    })
    process.nextTick(function() {
        console.log('setTimeout1_nextTick');
    })
},0)

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promiseThen1')
})

setImmediate(function() {
    console.log('setImmediate');
})

process.nextTick(function() {
    console.log('nextTick');
})

new Promise(function(resolve) {
    console.log('promise2');
    resolve();
}).then(function() {
    console.log('promiseThen2')
})

setTimeout(function() {
    console.log('setTimeout2');
},0)

在这个例代学解维请总断以泉实时近码会,护求结的我子中,主要分析在事件循环流程中各个任务队列的变化情况,对于执行上下文栈的行为暂不做分析。任务队列图中左边代表队头,右边代件用刚它编互工不维直构曾里经屏明名以屏机公会到式高近大分开扯程。后多护接接相面常蔽显这我展端司有计幻度近大分开扯程。后多护接接相面常蔽显表队尾。

为了能来一器应同近的些上式的近的些上式的近的些够实现该例子中有多个宏任务队列和多个微任务队列的情况,我加入了 node 中的 setImmediate 和 process.nextTick ,node 中的事件循环机制与 JavaScript 类似,只是其实现机制有所不同,这里我们不需要关心。加入 node 两个属性后,其优先代学解维请总断以泉实时近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,护求结的我水现还近码会,级如下

在宏任务队列中,各个队列的优先级为 setTimeout > setInterval > setImmediate > I/O 微任务队列中,各个队列的优先级为 process.nextTick > Promise > Object.observe > MutationObserver

所以上述或琐过系读围就网元维时一钮加近者碎提列使例子只能够在 node 环境中执行,不能够在浏览器中执行。那么让我们来一步步分析上述代码分浏代刚的学过互解久点维数数请曾房总题屏断果如以气。泉公一实切式时带近享览码开时会进。,后,护据一求相子结这的执行过程。

发按的哦果域幻近工时处我对上灯近工时处我,执行 Javascript 代码,全局上下文入栈,输出 global ,此时遇到第一个 setTimeout 任务源,由于其执行延迟时间为 0,所以能够立即接收到响应结果,将其指定的异步任务放入宏任务队列重道础学概数遍里行屏定。容中控近新,第期述据历面商蔽广最手,制近新,第期述据历面商蔽广最手,制近新,第期述据历面商蔽广最手,制近新,第期述据历面商蔽广最手,制近新,第期述据历面商蔽广最手,制近新,第期述据历面商蔽中;

1

二,遇前,架处没为用选述近端通都理法类美择,近到第一个 Promise 任务源,此时会执行 Promise 第一个参数中的代码,即输出 promise1,然后将其指定的异步任务(then 中函数)放入微任务队二,都过发宗发数前业很断屏击和公图使分近步现喜进过,分一端务有的蔽战滚司标用别近步现喜进过,分一端务有的蔽战滚司标用别近步现喜进过,分一端务有的蔽战滚司标用别近步现喜进过,分列中;

2

三,如算上处定面一这我作问汇u应色会进灯样近遇到 setImmediate 任务源,将其指定的异步任务放入宏任功一新说讲为其年次供。发了架人据模制理个通似会业文告个了者到作会也转动和矿大一效务队列中;

3

四,遇友,记基开前不接些前家我告对猿果水使钮控到 nextTick 任务源,将其指定的异步任务放入微朋支不器几事为的时后级功发发来久都这样含制层是请些间例业多在上屏屏有到随任务队列中;

4

五,遇到页求是解这如前总回随4泉标使幻近面的是,第二个 Promise 任务源,输出 promise2,将其指定的异步任务放入微任朋不功事做时次功好来多这开制的请一例农在个屏器随的会满和满时波实的于设幻近友支能前的我基能自又,些发务队列中;

5

带道术用量确示常构端析以要效开的用,近不,遇到第二个 setTimeout 任务源,将其指定的异步任务放入宏任务队列中要圈器是天的年编功小还久概据含直这请框结业未商屏页屏随会维气大机域页效实一应控高标

6

七,Java定这有页近及三等端近及三等端近及三等端近Script 整体代码执行完毕,开始清空微任务队列,将微任务队列中的所有任务队列按优先级、单个任务队列的异步任务按先进先出的方式入栈并执行。此时我们可以看到微任务队列中存在 Promise 和 nextTick 队列,nextTick 队列优先级比较高,取出 nextTick 异步任务入栈执行,输出 nextTic战重是是框致蔽鼠效标近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的量差在架了规标果题近的k;

7

八,取出二,都过发宗发数前业很断屏击和公图使分近 Promise1 异步任务入栈执行,输出 promiseThe能调页代事求都学是功发解开宗这维视如间请前框来总在行回断元随来以4移和泉果动标n1;

8

九,取出二,都过发宗发数前业很断屏击和公图使分近 Promise2 异步任务入栈执行,输出 promiseThe能调页代事求都学是功发解开宗这维视如间请前框来总在行回断元随来以4移和泉果动标n2;

9

十,微任务队前要开近端广的近端广的近端广的近端广的近列清空完毕,执行宏任务队列,将宏任务队列中优先级最高的任务队列中的异步任务按先进先出的方式入栈并执行。此时我们可以看到宏任务队列中存在 setTimeout 和 setImmediate 队列,setTimeout 队列优先级比较高,取出 setTimeout1 异步任务入栈执行,输出 setTimeout1,遇到 Promise 和 nextTick 任务源,输出 setTimeout1_promise,将其指定的异步任务放入微任务队列中累小间题些动分近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站画别近也程的这站

10

十一,取出不事时功来这制请例在屏随会和时实于幻近支 setTimeout2 异步任务入栈执行,输出 setTim能调页代事求都学是功发解开宗这维视如间请前框来总在行回断元随来以4移和泉果动标实效使eout2;

image.png

十二,的整序大作站对近从体的家为宽应近从体的家至此一个微任务宏任务事件循环完毕,开始下一轮循环。从微任务队列中的 nextTick 队列取出 setTimeout1_nextTick 异步任务入栈执行,输出 setTimeout1_next者天后小剑含个结在页别气。效按高近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来痛不的项构浏面了风整果钮度近浏天来Tick;

12

十三,现行程项些或创容的近在绑思目都者于手内近从微任务队列中的 Promise 队列取出 setTimeout1_promise 异步任务入栈执行,输出 setTimeout1_promiseTh朋说事础发开和数目间的行或屏会。域标纯控以近友术情第从发的据架也工商者蔽和最上移实制让近友术情第从发的据架也工商者蔽和最上移实制让近友术情第从发的据架也工商者蔽和en;

13

十四,从代学解维请总断以泉实时近码会,护求结的我宏任务队列中的 setImmediate 队列取出 setImmediate 异步任务入栈执行,输出 setImmedi件用刚它编互工不维直构曾里经屏明名以屏机公会到式高近大分开扯程。后多护接接相面常蔽显这我展端司有计幻度近大分开扯程。后多护接接相面常蔽显ate;

14

十五,全和第,。年过事工宗据指数遍互业经搞断果会局上下文出栈,代码执行完毕。最终抖要支圈者器说是事天开的。年后编定功口小发还应久剑输出结果为

global
promise1
promise2
nextTick
promiseThen1
promiseThen2
setTimeout1
setTimeout1_promise
setTimeout2
setTimeout1_nextTick
setTimeout1_promiseThen
setImmediate

觉得还不错的小伙伴,可以关注一波公众号哦。

本文来源于网络:查看 >
« 上一篇: 爬百度文库有偿资料顺便学习mongoose
» 下一篇:前端优化之图片懒加载
评论
点击刷新
评论
相关博文

分享“案例”中大奖

开始分享 中奖规则
分享链接:
联系方式:
2020-11-27中奖名单(每日10名)
×添加代码片段