<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一次搞懂- JS 事件循環(huán)之宏任務(wù)和微任務(wù)

          共 5467字,需瀏覽 11分鐘

           ·

          2021-05-11 20:31

          作者:九旬

          來源:SegmentFault 思否社區(qū)


          眾所周知,JS 是一門單線程語言,可是瀏覽器又能很好的處理異步請(qǐng)求,那么到底是為什么呢?

          JS 的執(zhí)行環(huán)境一般是瀏覽器和 Node.js,兩者稍有不同,這里只討論瀏覽器環(huán)境下的情況。

          JS 執(zhí)行過程中會(huì)產(chǎn)生兩種任務(wù),分別是:同步任務(wù)和異步任務(wù)。

          • 同步任務(wù):比如聲明語句、for、賦值等,讀取后依據(jù)從上到下從左到右,立即執(zhí)行。
          • 異步任務(wù):比如 ajax 網(wǎng)絡(luò)請(qǐng)求,setTimeout 定時(shí)函數(shù)等都屬于異步任務(wù)。異步任務(wù)會(huì)通過任務(wù)隊(duì)列(Event Queue)的機(jī)制(先進(jìn)先出的機(jī)制)來進(jìn)行協(xié)調(diào)。

          任務(wù)隊(duì)列(Event Queue)

          任務(wù)隊(duì)列中的任務(wù)也分為兩種,分別是:宏任務(wù)(Macro-take)和微任務(wù)(Micro-take)
          • 宏任務(wù)主要包括:scrip(JS 整體代碼)、setTimeout、setInterval、setImmediate、I/O、UI 交互
          • 微任務(wù)主要包括:Promise(重點(diǎn)關(guān)注)、process.nextTick(Node.js)、MutaionObserver
          任務(wù)隊(duì)列的執(zhí)行過程是:先執(zhí)行一個(gè)宏任務(wù),執(zhí)行過程中如果產(chǎn)出新的宏/微任務(wù),就將他們推入相應(yīng)的任務(wù)隊(duì)列,之后在執(zhí)行一隊(duì)微任務(wù),之后再執(zhí)行宏任務(wù),如此循環(huán)。以上不斷重復(fù)的過程就叫做 Event Loop(事件循環(huán))
          每一次的循環(huán)操作被稱為tick

          理解微任務(wù)和宏任務(wù)的執(zhí)行執(zhí)行過程

          console.log("script start");

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

          Promise.resolve()
            .then(function () {
              console.log("promise1");
            })
            .then(function () {
              console.log("promise2");
            });

          console.log("script end");
          按照上面的內(nèi)容,分析執(zhí)行步驟:
          1. 宏任務(wù):執(zhí)行整體代碼(相當(dāng)于<script>中的代碼):
            1. 輸出: script start
            2. 遇到 setTimeout,加入宏任務(wù)隊(duì)列,當(dāng)前宏任務(wù)隊(duì)列(setTimeout)
            3. 遇到 promise,加入微任務(wù),當(dāng)前微任務(wù)隊(duì)列(promise1)
            4. 輸出:script end
          2. 微任務(wù):執(zhí)行微任務(wù)隊(duì)列(promise1)
            1. 輸出:promise1,then 之后產(chǎn)生一個(gè)微任務(wù),加入微任務(wù)隊(duì)列,當(dāng)前微任務(wù)隊(duì)列(promise2)
            2. 執(zhí)行 then,輸出promise2
          3. 執(zhí)行渲染操作,更新界面(敲黑板劃重點(diǎn))。
          4. 宏任務(wù):執(zhí)行 setTimeout
            1. 輸出:setTimeout

          Promise 的執(zhí)行

          new Promise(..)中的代碼,也是同步代碼,會(huì)立即執(zhí)行。只有then之后的代碼,才是異步執(zhí)行的代碼,是一個(gè)微任務(wù)。
          console.log("script start");

          setTimeout(function () {
            console.log("timeout1");
          }, 10);

          new Promise((resolve) => {
            console.log("promise1");
            resolve();
            setTimeout(() => console.log("timeout2"), 10);
          }).then(function () {
            console.log("then1");
          });

          console.log("script end");
          步驟解析:
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù): [], 宏任務(wù):[<script>]
          1. 宏任務(wù):
            1. 輸出: script start
            2. 遇到 timeout1,加入宏任務(wù)
            3. 遇到 Promise,輸出promise1,直接 resolve,將 then 加入微任務(wù),遇到 timeout2,加入宏任務(wù)。
            4. 輸出script end
            5. 宏任務(wù)第一個(gè)執(zhí)行結(jié)束
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[then1],宏任務(wù)[timeou1, timeout2]
          1. 微任務(wù):
            1. 執(zhí)行 then1,輸出then1
            2. 微任務(wù)隊(duì)列清空
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[],宏任務(wù)[timeou1, timeout2]
          1. 宏任務(wù):
            1. 輸出timeout1
            2. 輸出timeout2
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[],宏任務(wù)[timeou2]
          1. 微任務(wù):
            1. 為空跳過
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[],宏任務(wù)[timeou2]
          1. 宏任務(wù):
            1. 輸出timeout2

          async/await 的執(zhí)行

          async 和 await 其實(shí)就是 Generator 和 Promise 的語法糖。
          async 函數(shù)和普通 函數(shù)沒有什么不同,他只是表示這個(gè)函數(shù)里有異步操作的方法,并返回一個(gè) Promise 對(duì)象
          翻譯過來其實(shí)就是:
          // async/await 寫法
          async function async1() {
            console.log("async1 start");
            await async2();
            console.log("async1 end");
          }
          // Promise 寫法
          async function async1() {
            console.log("async1 start");
            Promise.resolve(async2()).then(() => console.log("async1 end"));
          }
          看例子:
          async function async1() {
            console.log("async1 start");
            await async2();
            console.log("async1 end");
          }
          async function async2() {
            console.log("async2");
          }
          async1();
          setTimeout(() => {
            console.log("timeout");
          }, 0);
          new Promise(function (resolve) {
            console.log("promise1");
            resolve();
          }).then(function () {
            console.log("promise2");
          });
          console.log("script end");
          步驟解析:
          • 當(dāng)前任務(wù)隊(duì)列:宏任務(wù):[<script>],微任務(wù): []
          1. 宏任務(wù):
            1. 輸出:async1 start
            2. 遇到 async2,輸出:async2,并將 then(async1 end)加入微任務(wù)
            3. 遇到 setTimeout,加入宏任務(wù)。
            4. 遇到 Promise,輸出:promise1,直接 resolve,將 then(promise2)加入微任務(wù)
            5. 輸出:script end
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[promise2, async1 end],宏任務(wù)[timeout]
          1. 微任務(wù):
            1. 輸出:promise2
            2. promise2 出隊(duì)
            3. 輸出:async1 end
            4. async1 end 出隊(duì)
            5. 微任務(wù)隊(duì)列清空
          • 當(dāng)前任務(wù)隊(duì)列:微任務(wù)[],宏任務(wù)[timeout]
          1. 宏任務(wù):
            1. 輸出:timeout
            2. timeout 出隊(duì),宏任務(wù)清空
          "任務(wù)隊(duì)列"是一個(gè)事件的隊(duì)列(也可以理解成消息的隊(duì)列),IO設(shè)備完成一項(xiàng)任務(wù),就在"任務(wù)隊(duì)列"中添加一個(gè)事件,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了。主線程讀取"任務(wù)隊(duì)列",就是讀取里面有哪些事件。
          "任務(wù)隊(duì)列"中的事件,除了IO設(shè)備的事件以外,還包括一些用戶產(chǎn)生的事件(比如鼠標(biāo)點(diǎn)擊、頁面滾動(dòng)等等)。只要指定過回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入"任務(wù)隊(duì)列",等待主線程讀取。
          所謂"回調(diào)函數(shù)"(callback),就是那些會(huì)被主線程掛起來的代碼。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
          "任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過程基本上是自動(dòng)的,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程。但是,由于存在后文提到的"定時(shí)器"功能,主線程首先要檢查一下執(zhí)行時(shí)間,某些事件只有到了規(guī)定的時(shí)間,才能返回主線程。
          ----JavaScript中沒有任何代碼時(shí)立即執(zhí)行的,都是進(jìn)程空閑時(shí)盡快執(zhí)行

          setTimerout 并不準(zhǔn)確

          由上我們已經(jīng)知道了 setTimeout 是一個(gè)宏任務(wù),會(huì)被添加到宏任務(wù)隊(duì)列當(dāng)中去,按順序執(zhí)行,如果前面有。
          setTimeout() 的第二個(gè)參數(shù)是為了告訴 JavaScript 再過多長時(shí)間把當(dāng)前任務(wù)添加到隊(duì)列中。
          如果隊(duì)列是空的,那么添加的代碼會(huì)立即執(zhí)行;如果隊(duì)列不是空的,那么它就要等前面的代碼執(zhí)行完了以后再執(zhí)行。
          看代碼:
          const s = new Date().getSeconds();
          console.log("script start");
          new Promise((resolve) => {
            console.log("promise");
            resolve();
          }).then(() => {
            console.log("then1");
            while (true) {
              if (new Date().getSeconds() - s >= 4) {
                console.log("while");
                break;
              }
            }
          });
          setTimeout(() => {
            console.log("timeout");
          }, 2000);
          console.log("script end");
          因?yàn)閠hen是一個(gè)微任務(wù),會(huì)先于setTimeout執(zhí)行,所以,雖然setTimeout是在兩秒后加入的宏任務(wù),但是因?yàn)閠hen中的在while操作被延遲了4s,所以一直推遲到了4s秒后才執(zhí)行的setTimeout。
          所以輸出的順序是:script start、promise、script end、then1。
          四秒后輸出:while、timeout
          注意:關(guān)于 setTimeout 要補(bǔ)充的是,即便主線程為空,0 毫秒實(shí)際上也是達(dá)不到的。根據(jù) HTML 的標(biāo)準(zhǔn),最低是 4 毫秒。有興趣的同學(xué)可以自行了解。
          <!-- ### 異步渲染策略 -->
          <!-- 以 Vue 為例 nextTick -->

          總結(jié)

          有個(gè)小 tip:從規(guī)范來看,microtask 優(yōu)先于 task 執(zhí)行,所以如果有需要優(yōu)先執(zhí)行的邏輯,放入 microtask 隊(duì)列會(huì)比 task 更早的被執(zhí)行。
          最后的最后,記住,JavaScript 是一門單線程語言,異步操作都是放到事件循環(huán)隊(duì)列里面,等待主執(zhí)行棧來執(zhí)行的,并沒有專門的異步執(zhí)行線程。


          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本成人电影在线观看 | 成人视频黄网站国产偷拍 | 国产真实露脸乱子伦对白高清视频 | 亚洲国产AV电影 | 99re在线视频 |