<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>

          深入理解 JavaScript 之事件循環(huán)(Event Loop)

          共 4067字,需瀏覽 9分鐘

           ·

          2020-09-04 13:17

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

          作者:JackySummer




          前言


          這次繼續(xù)一步步回顧 JS 基礎(chǔ)知識(shí)點(diǎn),今天講的是 JS 中的事件循環(huán)。





          JavaScript 是單線程的


          JS 是一門單線程的非阻塞的腳本語(yǔ)言,這表示在同一時(shí)刻最多也只有一個(gè)代碼段執(zhí)行。





          為什么 JavaScript 是單線程的


          如果 JS 是多線程的,因?yàn)?JS 有 DOM API 可以操作 DOM,如果同時(shí)開了兩個(gè)線程同時(shí)操作 DOM 的話,一個(gè)線程刪除了當(dāng)前的 DOM 節(jié)點(diǎn),另一個(gè)線程要操作當(dāng)前的 DOM,那么就會(huì)有矛盾到底以哪個(gè)線程為主。為了避免這種情況出現(xiàn),JS 就被設(shè)計(jì)為單線程,而且單線程執(zhí)行效率高?,F(xiàn)在雖然也有 web worker 標(biāo)準(zhǔn)的出現(xiàn),但它也有很多限制,受主線程控制,是主線程的子線程。





          JS 如何處理異步任務(wù)


          JS 是單線程,那么非阻塞怎么體現(xiàn)呢?如果 JS 是阻塞的,那么 JS 發(fā)起一個(gè)異步 IO 請(qǐng)求,在等待結(jié)果返回的這個(gè)時(shí)間段,后面的代碼就無(wú)法執(zhí)行了,而 JS 主線程和渲染進(jìn)程是互斥的,因此可能造成瀏覽器假死的狀態(tài)。事實(shí) JS 是非阻塞的,那它要怎么實(shí)現(xiàn)異步任務(wù)呢,靠的就是事件循環(huán)。





          事件循環(huán)


          事件循環(huán)就是通過(guò)異步執(zhí)行任務(wù)的方法來(lái)解決單線程的弊端的。


          1. 一開始整個(gè)腳本作為一個(gè)宏任務(wù)執(zhí)行
          2. 執(zhí)行過(guò)程中同步代碼直接執(zhí)行,宏任務(wù)進(jìn)入宏任務(wù)隊(duì)列,微任務(wù)進(jìn)入微任務(wù)隊(duì)列
          3. 當(dāng)前宏任務(wù)執(zhí)行完出隊(duì),讀取微任務(wù)列表,有則依次執(zhí)行,直到全部執(zhí)行完
          4. 執(zhí)行瀏覽器 UI 線程的渲染工作
          5. 檢查是否有 Web Worker 任務(wù),有則執(zhí)行
          6. 執(zhí)行完本輪的宏任務(wù),回到第 2 步,繼續(xù)依此循環(huán),直到宏任務(wù)和微任務(wù)隊(duì)列都為空





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


          JS 引擎把所有任務(wù)分成兩類,一類叫宏任務(wù)(macroTask),一類叫微任務(wù)(microTask)


          宏任務(wù)


          • script(整體代碼)
          • setTimeout/setInterval
          • I/O
          • UI 渲染
          • postMessage
          • MessageChannel
          • requestAnimationFrame
          • setImmediate(Node.js 環(huán)境)


          微任務(wù)


          • new Promise().then()
          • MutaionObserver
          • process.nextTick(Node.js 環(huán)境)





          經(jīng)典題目 1


          關(guān)于更細(xì)節(jié)的描述我就不寫了,因?yàn)橛懈玫奈恼驴梢詤⒖紝W(xué)習(xí):


          • 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制


          簡(jiǎn)單介紹后,接下來(lái)就來(lái)看幾道經(jīng)典題目:

          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')



          1. 整體 script 作為第一個(gè)宏任務(wù)進(jìn)入主線程,輸出script start
          2. 遇到 setTimeout,setTimeout 為宏任務(wù),加入宏任務(wù)隊(duì)列
          3. 遇到 Promise,其 then 回調(diào)函數(shù)加入到微任務(wù)隊(duì)列;第二個(gè) then 回調(diào)函數(shù)也加入到微任務(wù)隊(duì)列
          4. 繼續(xù)往下執(zhí)行,輸出script end
          5. 檢測(cè)微任務(wù)隊(duì)列,輸出promise1、promise2
          6. 進(jìn)入下一輪循環(huán),執(zhí)行 setTimeout 中的代碼,輸出setTimeout


          最后執(zhí)行結(jié)果為:


          script startscript endpromise1promise2setTimeout





          經(jīng)典題目 2


          來(lái)看一道面試的經(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')


          1. 整體 script 作為第一個(gè)宏任務(wù)進(jìn)入主線程,代碼自上而下執(zhí)行,執(zhí)行同步代碼,輸出?script start
          2. 遇到 setTimeout,加入到宏任務(wù)隊(duì)列
          3. 執(zhí)行 async1(),輸出async1 start;然后遇到await async2(),await 實(shí)際上是讓出線程的標(biāo)志,首先執(zhí)行 async2(),輸出async2;把 async2() 后面的代碼console.log('async1 end')加入微任務(wù)隊(duì)列中,跳出整個(gè) async 函數(shù)。(async 和 await 本身就是 promise+generator 的語(yǔ)法糖。所以 await 后面的代碼是微任務(wù)。)
          4. 繼續(xù)執(zhí)行,遇到 new Promise,輸出promise1,把.then()之后的代碼加入到微任務(wù)隊(duì)列中
          5. 繼續(xù)往下執(zhí)行,輸出script end。接著讀取微任務(wù)隊(duì)列,輸出async1 end,promise2,執(zhí)行完本輪的宏任務(wù)。繼續(xù)執(zhí)行下一輪宏任務(wù)的代碼,輸出setTimeout


          最后執(zhí)行結(jié)果為:


          script startasync1 startasync2promise1script endasync1 endpromise2setTimeout





          經(jīng)典題目 3


          我們來(lái)看下面一段代碼:


          setTimeout(function () {  console.log('timer1')}, 0)
          requestAnimationFrame(function () { console.log('requestAnimationFrame')})
          setTimeout(function () { console.log('timer2')}, 0)
          new Promise(function executor(resolve) { console.log('promise 1') resolve() console.log('promise 2')}).then(function () { console.log('promise then')})
          console.log('end')


          • 整體 script 代碼執(zhí)行,開局新增三個(gè)宏任務(wù),兩個(gè) setTimeout 和一個(gè) requestAnimationFrame
          • 遇到 Promise,先輸出promise1, promise2,加把 then 回調(diào)加入微任務(wù)隊(duì)列。
          • 繼續(xù)往下執(zhí)行,輸出end
          • 執(zhí)行 promise 的 then 回調(diào),輸出promise then
          • 接下來(lái)剩三個(gè)宏任務(wù),我們可以知道的是timer1會(huì)比timer2先執(zhí)行,那么requestAnimationFrame呢?


          當(dāng)每一輪事件循環(huán)的微任務(wù)隊(duì)列被清空后,有可能發(fā)生 UI 渲染,也就是說(shuō)執(zhí)行任務(wù)的耗時(shí)會(huì)影響視圖渲染的時(shí)機(jī)。


          通常瀏覽器以每秒 60 幀(60fps)的速率刷新頁(yè)面,這個(gè)幀率最適合人眼交互,大概 1000ms/60?約等于 16.7ms 渲染一幀,如果要讓用戶看得順暢,單個(gè)宏任務(wù)及它相應(yīng)的微任務(wù)最好能在 16.7ms 內(nèi)完成。


          requestAnimationFrame 是什么?


          window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行。
          requestAnimationFrame 的基本思想是 讓頁(yè)面重繪的頻率和刷新頻率保持同步,相比 setTimeout,requestAnimationFrame 最大的優(yōu)勢(shì)是由系統(tǒng)來(lái)決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。


          但這個(gè)也不是每輪事件循環(huán)都會(huì)執(zhí)行 UI 渲染,不同瀏覽器有自己的優(yōu)化策略,比如把幾次的視圖更新累積到一起重繪,重繪之前會(huì)通知 requestAnimationFrame 執(zhí)行回調(diào)函數(shù),也就是說(shuō) requestAnimationFrame 回調(diào)的執(zhí)行時(shí)機(jī)是在一次或多次事件循環(huán)的 UI render 階段。


          在我的谷歌瀏覽器執(zhí)行結(jié)果:


          promise 1promise 2endpromise thenrequestAnimationFrametimer1timer2


          在我的火狐瀏覽器執(zhí)行結(jié)果:


          promise 1promise 2endpromise thentimer1timer2requestAnimationFrame


          谷歌瀏覽器中的結(jié)果 requestAnimationFrame()是在一次事件循環(huán)后執(zhí)行,火狐瀏覽器中的結(jié)果是在三次事件循環(huán)結(jié)束后執(zhí)行。


          可以知道,瀏覽器只保證 requestAnimationFrame 的回調(diào)在重繪之前執(zhí)行,但沒(méi)有確定的時(shí)間,何時(shí)重繪由瀏覽器決定。





          參考文章


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





          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動(dòng)和交流。

          -?END -

          瀏覽 48
          點(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>
                  在线天堂a 8 | 日韩三级片网站在线观看 | 午夜gogo | 香丁五月在线 | 国产一级片国产特级片 |