<em id="09ttv"></em>
    <sup id="09ttv"><pre id="09ttv"></pre></sup>
    <dd id="09ttv"></dd>

        • JavaScript中的Event Loop(事件循環(huán))機制

          2020-5-25    seo達人

          事件循環(huán)

          JavaScript是單線程,非阻塞的

          瀏覽器的事件循環(huán)


          執(zhí)行棧和事件隊列

          宏任務(wù)和微任務(wù)

          node環(huán)境下的事件循環(huán)


          和瀏覽器環(huán)境有何不同

          事件循環(huán)模型

          宏任務(wù)和微任務(wù)

          經(jīng)典題目分析

          1. JavaScript是單線程,非阻塞的

          單線程:


          JavaScript的主要用途是與用戶互動,以及操作DOM。如果它是多線程的會有很多復(fù)雜的問題要處理,比如有兩個線程同時操作DOM,一個線程刪除了當(dāng)前的DOM節(jié)點,一個線程是要操作當(dāng)前的DOM階段,最后以哪個線程的操作為準(zhǔn)?為了避免這種,所以JS是單線程的。即使H5提出了web worker標(biāo)準(zhǔn),它有很多限制,受主線程控制,是主線程的子線程。


          非阻塞:通過 event loop 實現(xiàn)。


          2. 瀏覽器的事件循環(huán)

          執(zhí)行棧和事件隊列

          為了更好地理解Event Loop,請看下圖(轉(zhuǎn)引自Philip Roberts的演講 《Help, I'm stuck in an event-loop》)

          Help, I'm stuck in an event-loop


          執(zhí)行棧: 同步代碼的執(zhí)行,按照順序添加到執(zhí)行棧中


          function a() {

             b();

             console.log('a');

          }

          function b() {

             console.log('b')

          }

          a();

          我們可以通過使用 Loupe(Loupe是一種可視化工具,可以幫助您了解JavaScript的調(diào)用堆棧/事件循環(huán)/回調(diào)隊列如何相互影響)工具來了解上面代碼的執(zhí)行情況。


          調(diào)用情況


          執(zhí)行函數(shù) a()先入棧

          a()中先執(zhí)行函數(shù) b() 函數(shù)b() 入棧

          執(zhí)行函數(shù)b(), console.log('b') 入棧

          輸出 b, console.log('b')出棧

          函數(shù)b() 執(zhí)行完成,出棧

          console.log('a') 入棧,執(zhí)行,輸出 a, 出棧

          函數(shù)a 執(zhí)行完成,出棧。

          事件隊列: 異步代碼的執(zhí)行,遇到異步事件不會等待它返回結(jié)果,而是將這個事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。當(dāng)異步事件返回結(jié)果,將它放到事件隊列中,被放入事件隊列不會立刻執(zhí)行起回調(diào),而是等待當(dāng)前執(zhí)行棧中所有任務(wù)都執(zhí)行完畢,主線程空閑狀態(tài),主線程會去查找事件隊列中是否有任務(wù),如果有,則取出排在第一位的事件,并把這個事件對應(yīng)的回調(diào)放到執(zhí)行棧中,然后執(zhí)行其中的同步代碼。


          我們再上面代碼的基礎(chǔ)上添加異步事件,


          function a() {

             b();

             console.log('a');

          }

          function b() {

             console.log('b')

             setTimeout(function() {

                 console.log('c');

             }, 2000)

          }

          a();

          此時的執(zhí)行過程如下

          img


          我們同時再加上點擊事件看一下運行的過程


          $.on('button', 'click', function onClick() {

             setTimeout(function timer() {

                 console.log('You clicked the button!');    

             }, 2000);

          });


          console.log("Hi!");


          setTimeout(function timeout() {

             console.log("Click the button!");

          }, 5000);


          console.log("Welcome to loupe.");

          img


          簡單用下面的圖進行一下總結(jié)


          執(zhí)行棧和事件隊列


          宏任務(wù)和微任務(wù)

          為什么要引入微任務(wù),只有一種類型的任務(wù)不行么?


          頁面渲染事件,各種IO的完成事件等隨時被添加到任務(wù)隊列中,一直會保持先進先出的原則執(zhí)行,我們不能準(zhǔn)確地控制這些事件被添加到任務(wù)隊列中的位置。但是這個時候突然有高優(yōu)先級的任務(wù)需要盡快執(zhí)行,那么一種類型的任務(wù)就不合適了,所以引入了微任務(wù)隊列。


          不同的異步任務(wù)被分為:宏任務(wù)和微任務(wù)

          宏任務(wù):


          script(整體代碼)

          setTimeout()

          setInterval()

          postMessage

          I/O

          UI交互事件

          微任務(wù):


          new Promise().then(回調(diào))

          MutationObserver(html5 新特性)

          運行機制

          異步任務(wù)的返回結(jié)果會被放到一個任務(wù)隊列中,根據(jù)異步事件的類型,這個事件實際上會被放到對應(yīng)的宏任務(wù)和微任務(wù)隊列中去。


          在當(dāng)前執(zhí)行棧為空時,主線程會查看微任務(wù)隊列是否有事件存在


          存在,依次執(zhí)行隊列中的事件對應(yīng)的回調(diào),直到微任務(wù)隊列為空,然后去宏任務(wù)隊列中取出最前面的事件,把當(dāng)前的回調(diào)加到當(dāng)前指向棧。

          如果不存在,那么再去宏任務(wù)隊列中取出一個事件并把對應(yīng)的回到加入當(dāng)前執(zhí)行棧;

          當(dāng)前執(zhí)行棧執(zhí)行完畢后時會立刻處理所有微任務(wù)隊列中的事件,然后再去宏任務(wù)隊列中取出一個事件。同一次事件循環(huán)中,微任務(wù)永遠在宏任務(wù)之前執(zhí)行。


          在事件循環(huán)中,每進行一次循環(huán)操作稱為 tick,每一次 tick 的任務(wù)處理模型是比較復(fù)雜的,但關(guān)鍵步驟如下:


          執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊列中獲取)

          執(zhí)行過程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊列中

          宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)

          當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染

          渲染完畢后,JS線程繼續(xù)接管,開始下一個宏任務(wù)(從事件隊列中獲取)

          簡單總結(jié)一下執(zhí)行的順序:

          執(zhí)行宏任務(wù),然后執(zhí)行該宏任務(wù)產(chǎn)生的微任務(wù),若微任務(wù)在執(zhí)行過程中產(chǎn)生了新的微任務(wù),則繼續(xù)執(zhí)行微任務(wù),微任務(wù)執(zhí)行完畢后,再回到宏任務(wù)中進行下一輪循環(huán)。


          宏任務(wù)和微任務(wù)


          深入理解js事件循環(huán)機制(瀏覽器篇) 這邊文章中有個特別形象的動畫,大家可以看著理解一下。


          console.log('start')


          setTimeout(function() {

           console.log('setTimeout')

          }, 0)


          Promise.resolve().then(function() {

           console.log('promise1')

          }).then(function() {

           console.log('promise2')

          })


          console.log('end')

          瀏覽器事件循環(huán)


          全局代碼壓入執(zhí)行棧執(zhí)行,輸出 start

          setTimeout壓入 macrotask隊列,promise.then 回調(diào)放入 microtask隊列,最后執(zhí)行 console.log('end'),輸出 end

          調(diào)用棧中的代碼執(zhí)行完成(全局代碼屬于宏任務(wù)),接下來開始執(zhí)行微任務(wù)隊列中的代碼,執(zhí)行promise回調(diào),輸出 promise1, promise回調(diào)函數(shù)默認(rèn)返回 undefined, promise狀態(tài)變成 fulfilled ,觸發(fā)接下來的 then回調(diào),繼續(xù)壓入 microtask隊列,此時產(chǎn)生了新的微任務(wù),會接著把當(dāng)前的微任務(wù)隊列執(zhí)行完,此時執(zhí)行第二個 promise.then回調(diào),輸出 promise2

          此時,microtask隊列 已清空,接下來會會執(zhí)行 UI渲染工作(如果有的話),然后開始下一輪 event loop, 執(zhí)行 setTimeout的回調(diào),輸出 setTimeout

          最后的執(zhí)行結(jié)果如下


          start

          end

          promise1

          promise2

          setTimeout

          node環(huán)境下的事件循環(huán)

          和瀏覽器環(huán)境有何不同

          表現(xiàn)出的狀態(tài)與瀏覽器大致相同。不同的是 node 中有一套自己的模型。node 中事件循環(huán)的實現(xiàn)依賴 libuv 引擎。Node的事件循環(huán)存在幾個階段。


          如果是node10及其之前版本,microtask會在事件循環(huán)的各個階段之間執(zhí)行,也就是一個階段執(zhí)行完畢,就會去執(zhí)行 microtask隊列中的任務(wù)。


          node版本更新到11之后,Event Loop運行原理發(fā)生了變化,一旦執(zhí)行一個階段里的一個宏任務(wù)(setTimeout,setInterval和setImmediate)就立刻執(zhí)行微任務(wù)隊列,跟瀏覽器趨于一致。下面例子中的代碼是按照的去進行分析的。


          事件循環(huán)模型

          ┌───────────────────────┐

          ┌─>│        timers         │

          │  └──────────┬────────────┘

          │  ┌──────────┴────────────┐

          │  │     I/O callbacks     │

          │  └──────────┬────────────┘

          │  ┌──────────┴────────────┐

          │  │     idle, prepare     │

          │  └──────────┬────────────┘      ┌───────────────┐

          │  ┌──────────┴────────────┐      │   incoming:   │

          │  │         poll          │<──connections───     │

          │  └──────────┬────────────┘      │   data, etc.  │

          │  ┌──────────┴────────────┐      └───────────────┘

          │  │        check          │

          │  └──────────┬────────────┘

          │  ┌──────────┴────────────┐

          └──┤    close callbacks    │

            └───────────────────────┘

          事件循環(huán)各階段詳解

          node中事件循環(huán)的順序


          外部輸入數(shù)據(jù) --> 輪詢階段(poll) --> 檢查階段(check) --> 關(guān)閉事件回調(diào)階段(close callback) --> 定時器檢查階段(timer) --> I/O 事件回調(diào)階段(I/O callbacks) --> 閑置階段(idle, prepare) --> 輪詢階段...


          這些階段大致的功能如下:


          定時器檢測階段(timers): 這個階段執(zhí)行定時器隊列中的回調(diào)如 setTimeout() 和 setInterval()。

          I/O事件回調(diào)階段(I/O callbacks): 這個階段執(zhí)行幾乎所有的回調(diào)。但是不包括close事件,定時器和setImmediate()的回調(diào)。

          閑置階段(idle, prepare): 這個階段僅在內(nèi)部使用,可以不必理會

          輪詢階段(poll): 等待新的I/O事件,node在一些特殊情況下會阻塞在這里。

          檢查階段(check): setImmediate()的回調(diào)會在這個階段執(zhí)行。

          關(guān)閉事件回調(diào)階段(close callbacks): 例如socket.on('close', ...)這種close事件的回調(diào)

          poll:

          這個階段是輪詢時間,用于等待還未返回的 I/O 事件,比如服務(wù)器的回應(yīng)、用戶移動鼠標(biāo)等等。

          這個階段的時間會比較長。如果沒有其他異步任務(wù)要處理(比如到期的定時器),會一直停留在這個階段,等待 I/O 請求返回結(jié)果。

          check:

          該階段執(zhí)行setImmediate()的回調(diào)函數(shù)。


          close:

          該階段執(zhí)行關(guān)閉請求的回調(diào)函數(shù),比如socket.on('close', ...)。


          timer階段:

          這個是定時器階段,處理setTimeout()和setInterval()的回調(diào)函數(shù)。進入這個階段后,主線程會檢查一下當(dāng)前時間,是否滿足定時器的條件。如果滿足就執(zhí)行回調(diào)函數(shù),否則就離開這個階段。


          I/O callback階段:

          除了以下的回調(diào)函數(shù),其他都在這個階段執(zhí)行:


          setTimeout()和setInterval()的回調(diào)函數(shù)

          setImmediate()的回調(diào)函數(shù)

          用于關(guān)閉請求的回調(diào)函數(shù),比如socket.on('close', ...)

          宏任務(wù)和微任務(wù)

          宏任務(wù):


          setImmediate

          setTimeout

          setInterval

          script(整體代碼)

          I/O 操作等。

          微任務(wù):


          process.nextTick

          new Promise().then(回調(diào))

          Promise.nextTick, setTimeout, setImmediate的使用場景和區(qū)別

          Promise.nextTick

          process.nextTick 是一個獨立于 eventLoop 的任務(wù)隊列。

          在每一個 eventLoop 階段完成后會去檢查 nextTick 隊列,如果里面有任務(wù),會讓這部分任務(wù)優(yōu)先于微任務(wù)執(zhí)行。

          是所有異步任務(wù)中最快執(zhí)行的。


          setTimeout:

          setTimeout()方法是定義一個回調(diào),并且希望這個回調(diào)在我們所指定的時間間隔后第一時間去執(zhí)行。


          setImmediate:

          setImmediate()方法從意義上將是立刻執(zhí)行的意思,但是實際上它卻是在一個固定的階段才會執(zhí)行回調(diào),即poll階段之后。


          經(jīng)典題目分析

          一. 下面代碼輸出什么

          async function async1() {

             console.log('async1 start');

             await async2();

             console.log('async1 end');

          }

          async function async2() {

             console.log('async2');

          }

          console.log('script start');

          setTimeout(function() {

             console.log('setTimeout');

          }, 0)

          async1();

          new Promise(function(resolve) {

             console.log('promise1');

             resolve();

          }).then(function() {

             console.log('promise2');

          });

          console.log('script end');

          先執(zhí)行宏任務(wù)(當(dāng)前代碼塊也算是宏任務(wù)),然后執(zhí)行當(dāng)前宏任務(wù)產(chǎn)生的微任務(wù),然后接著執(zhí)行宏任務(wù)


          從上往下執(zhí)行代碼,先執(zhí)行同步代碼,輸出 script start

          遇到setTimeout,現(xiàn)把 setTimeout 的代碼放到宏任務(wù)隊列中

          執(zhí)行 async1(),輸出 async1 start, 然后執(zhí)行 async2(), 輸出 async2,把 async2() 后面的代碼 console.log('async1 end')放到微任務(wù)隊列中

          接著往下執(zhí)行,輸出 promise1,把 .then()放到微任務(wù)隊列中;注意Promise本身是同步的立即執(zhí)行函數(shù),.then是異步執(zhí)行函數(shù)

          接著往下執(zhí)行, 輸出 script end。同步代碼(同時也是宏任務(wù))執(zhí)行完成,接下來開始執(zhí)行剛才放到微任務(wù)中的代碼

          依次執(zhí)行微任務(wù)中的代碼,依次輸出 async1 end、 promise2, 微任務(wù)中的代碼執(zhí)行完成后,開始執(zhí)行宏任務(wù)中的代碼,輸出 setTimeout

          最后的執(zhí)行結(jié)果如下


          script start

          async1 start

          async2

          promise1

          script end

          async1 end

          promise2

          setTimeout

          二. 下面代碼輸出什么

          console.log('start');

          setTimeout(() => {

             console.log('children2');

             Promise.resolve().then(() => {

                 console.log('children3');

             })

          }, 0);


          new Promise(function(resolve, reject) {

             console.log('children4');

             setTimeout(function() {

                 console.log('children5');

                 resolve('children6')

             }, 0)

          }).then((res) => {

             console.log('children7');

             setTimeout(() => {

                 console.log(res);

             }, 0)

          })

          這道題跟上面題目不同之處在于,執(zhí)行代碼會產(chǎn)生很多個宏任務(wù),每個宏任務(wù)中又會產(chǎn)生微任務(wù)


          從上往下執(zhí)行代碼,先執(zhí)行同步代碼,輸出 start

          遇到setTimeout,先把 setTimeout 的代碼放到宏任務(wù)隊列①中

          接著往下執(zhí)行,輸出 children4, 遇到setTimeout,先把 setTimeout 的代碼放到宏任務(wù)隊列②中,此時.then并不會被放到微任務(wù)隊列中,因為 resolve是放到 setTimeout中執(zhí)行的

          代碼執(zhí)行完成之后,會查找微任務(wù)隊列中的事件,發(fā)現(xiàn)并沒有,于是開始執(zhí)行宏任務(wù)①,即第一個 setTimeout, 輸出 children2,此時,會把 Promise.resolve().then放到微任務(wù)隊列中。

          宏任務(wù)①中的代碼執(zhí)行完成后,會查找微任務(wù)隊列,于是輸出 children3;然后開始執(zhí)行宏任務(wù)②,即第二個 setTimeout,輸出 children5,此時將.then放到微任務(wù)隊列中。

          宏任務(wù)②中的代碼執(zhí)行完成后,會查找微任務(wù)隊列,于是輸出 children7,遇到 setTimeout,放到宏任務(wù)隊列中。此時微任務(wù)執(zhí)行完成,開始執(zhí)行宏任務(wù),輸出 children6;

          最后的執(zhí)行結(jié)果如下


          start

          children4

          children2

          children3

          children5

          children7

          children6

          三. 下面代碼輸出什么

          const p = function() {

             return new Promise((resolve, reject) => {

                 const p1 = new Promise((resolve, reject) => {

                     setTimeout(() => {

                         resolve(1)

                     }, 0)

                     resolve(2)

                 })

                 p1.then((res) => {

                     console.log(res);

                 })

                 console.log(3);

                 resolve(4);

             })

          }



          p().then((res) => {

             console.log(res);

          })

          console.log('end');

          執(zhí)行代碼,Promise本身是同步的立即執(zhí)行函數(shù),.then是異步執(zhí)行函數(shù)。遇到setTimeout,先把其放入宏任務(wù)隊列中,遇到p1.then會先放到微任務(wù)隊列中,接著往下執(zhí)行,輸出 3

          遇到 p().then 會先放到微任務(wù)隊列中,接著往下執(zhí)行,輸出 end

          同步代碼塊執(zhí)行完成后,開始執(zhí)行微任務(wù)隊列中的任務(wù),首先執(zhí)行 p1.then,輸出 2, 接著執(zhí)行p().then, 輸出 4

          微任務(wù)執(zhí)行完成后,開始執(zhí)行宏任務(wù),setTimeout, resolve(1),但是此時 p1.then已經(jīng)執(zhí)行完成,此時 1不會輸出。

          最后的執(zhí)行結(jié)果如下


          3

          end

          2

          4

          你可以將上述代碼中的 resolve(2)注釋掉, 此時 1才會輸出,輸出結(jié)果為 3 end 4 1。


          const p = function() {

             return new Promise((resolve, reject) => {

                 const p1 = new Promise((resolve, reject) => {

                     setTimeout(() => {

                         resolve(1)

                     }, 0)

                 })

                 p1.then((res) => {

                     console.log(res);

                 })

                 console.log(3);

                 resolve(4);

             })

          }



          p().then((res) => {

             console.log(res);

          })

          console.log('end');

          3

          end

          4

          1

          最后強烈推薦幾個非常好的講解 event loop 的視頻:


          What the heck is the event loop anyway? | Philip Roberts | JSConf EU

          Jake Archibald: In The Loop - JSConf.Asia

          日歷

          鏈接

          個人資料

          藍藍設(shè)計的小編 http://m.sdgs6788.com

          存檔

          亚洲AV无码1区2区久久| 综合人妻久久一区二区精品| 亚洲国产精品久久| 亚洲伊人久久综合影院| 综合网日日天干夜夜久久| 国产精品久久毛片完整版| 亚洲精品无码久久久久AV麻豆| 亚洲午夜久久久久久噜噜噜| 韩国三级中文字幕hd久久精品| 区亚洲欧美一级久久精品亚洲精品成人网久久久久 | 国内精品久久久久久麻豆| 久久久久国产精品麻豆AR影院| 久久这里只有精品首页| 91精品国产91久久久久久青草| 欧美亚洲国产精品久久高清| 青青青国产成人久久111网站| 77777亚洲午夜久久多人| 久久人搡人人玩人妻精品首页| 午夜精品久久久久成人| 日韩AV无码久久一区二区| 亚洲精品美女久久久久99小说| 青青草原1769久久免费播放| 亚洲精品乱码久久久久久| 国产精品久久久久久久久软件| 99久久精品免费看国产一区二区三区 | 久久99精品国产99久久6男男| 国产精品久久久香蕉| 热RE99久久精品国产66热| 91精品久久久久久无码| 色综合久久久久网| 国产精品99久久免费观看| 三上悠亚久久精品| 久久久久久亚洲AV无码专区| 综合久久国产九一剧情麻豆| 久久天天躁夜夜躁狠狠躁2022| 色8激情欧美成人久久综合电| 久久夜色精品国产亚洲av| 欧美激情精品久久久久久久| 亚洲国产小视频精品久久久三级| 久久夜色撩人精品国产小说| 人妻无码精品久久亚瑟影视|