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

          從 Event Loop 角度解讀 Vue NextTick 源碼

          共 5957字,需瀏覽 12分鐘

           ·

          2021-07-06 16:48

          點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)

          回復(fù)加群,加入前端Q技術(shù)交流群



          來源:小卒先生

          https://juejin.cn/post/6963542300073033764

          解讀背景

          1. 在學(xué)習(xí) vue 源碼,nextTick 方法借助了瀏覽器的 event loop 事件循環(huán)做到了異步更新。
          2. 在公司面試的時(shí)候,筆試題最喜歡出關(guān)于 JavaScript 運(yùn)行機(jī)制,Promise/A+ 等關(guān)于 event loop 線程的題目。
          3. 學(xué)會(huì) nextTick 原理幫助定位 BUG , 使用 Vue 會(huì)更加靈活。

          什么是 event loop

          先看一張圖 (來自 mr.z 大佬)

          v2-d1ca0d6b13501044a5f74c99becbcd3d_b.gif
          1. 先執(zhí)行同步阻塞任務(wù),同步任務(wù)會(huì)等待上一個(gè)執(zhí)行完畢以后執(zhí)行下一個(gè),當(dāng)同步任務(wù)執(zhí)行完畢,再執(zhí)行異步任務(wù),遇到異步任務(wù)會(huì)將異步任務(wù)的回調(diào)函數(shù)注冊(cè)在異步任務(wù)隊(duì)列里。注意,如果主線程上沒有同步任務(wù)會(huì)直接調(diào)用異步任務(wù)的微任務(wù)。
          2. 執(zhí)行宏任務(wù),遇到微任務(wù)將都添加到微任務(wù)隊(duì)列里。
          3. 開始執(zhí)行微任務(wù)隊(duì)列,當(dāng)宏任務(wù)執(zhí)行完后執(zhí)行微任務(wù)隊(duì)列,直到微任務(wù)隊(duì)列全部執(zhí)行完,微任務(wù)隊(duì)列為空。
          4. 執(zhí)行宏任務(wù),如果在執(zhí)行宏任務(wù)期間有微任務(wù),將微任務(wù)添加到微任務(wù)隊(duì)列里,執(zhí)行完宏任務(wù)之后執(zhí)行微任務(wù),直到微任務(wù)隊(duì)列全部執(zhí)行完。
          5. 繼續(xù)執(zhí)行宏任務(wù)隊(duì)列。

          重復(fù)2, 3, 4,5……直到宏微任務(wù)為空。

          $nextTick 的實(shí)現(xiàn)原理

          從字面意思理解,next 下一個(gè),tick 滴答(鐘表)來源于定時(shí)器的周期性中斷(輸出脈沖),一次中斷表示一個(gè) tick,也被稱做一個(gè)“時(shí)鐘滴答”),nextTick 顧名思義就是下一個(gè)時(shí)鐘滴答??丛创a,在 Vue 2.x 版本中,nextTicksrc\core\util 中的一個(gè)單獨(dú)的文件 next-tick.js ,可見 nextTick 的重要性,雖然短短 200 多行,尤大卻單獨(dú)創(chuàng)建一個(gè)文件去維護(hù)。

          接下來我們來看整個(gè)文件。

          1. 聲明了三個(gè)全局變量,callbacks: [] ,pending: Boolean,timerFunc: undefined
          2. 聲明了一個(gè)函數(shù) flushCallbacks。
          3. 一堆 **if,else if **判斷。
          4. 拋出了一個(gè)函數(shù) nextTick。

          nextTick 函數(shù)

          未命名文件 (1).png
          1. 聲明一個(gè)局部變量 _resolve
          2. 把所有回調(diào)函數(shù)壓進(jìn) callbacks 中,以棧的形式的存儲(chǔ)所有 callback
          3. 當(dāng) pendingfalse 時(shí),執(zhí)行 timerFunc 函數(shù)。
          4. 當(dāng)沒有 callback 的時(shí)候,返回一個(gè) Promise 的調(diào)用方式,可以用 .then 接收。

          timerFunc 函數(shù)

          我們開始說了,timerFunc 為全局變量,現(xiàn)在調(diào)用 timerFunc ,timerFunc 是什么時(shí)候被賦值為一個(gè)函數(shù),并且函數(shù)里執(zhí)行代碼又是什么?

          我們看到,這段判斷代碼總共有四個(gè)分支,四個(gè)分支里對(duì) timerFunc 有不同的賦值,我們先來看第一個(gè)分支。

          Promise 分支

          if (typeof Promise !== 'undefined' && isNative(Promise)) {
            const p = Promise.resolve()
            timerFunc = () => {
              p.then(flushCallbacks)
              // In problematic UIWebViews, Promise.then doesn't completely break, but
              // it can get stuck in a weird state where callbacks are pushed into the
              // microtask queue but the queue isn't being flushed, until the browser
              // needs to do some other work, e.g. handle a timer. Therefore we can
              // "force" the microtask queue to be flushed by adding an empty timer.
              if (isIOS) setTimeout(noop)
            }
            isUsingMicroTask = true
          }
          復(fù)制代碼
          1. 判斷環(huán)境是否支持 Promise 并且 Promise 是否為原生。
          2. 使用 Promise 異步調(diào)用 flushCallbacks 函數(shù)。
          3. 當(dāng)執(zhí)行環(huán)境是 iPhone 等,使用 setTimeout 異步調(diào)用 noop ,iOS 中在一些異常的webview 中,promise 結(jié)束后任務(wù)隊(duì)列并沒有刷新所以強(qiáng)制執(zhí)行 setTimeout 刷新任務(wù)隊(duì)列。

          MutationObserver 分支

          else if (!isIE && typeof MutationObserver !== 'undefined' && (
            isNative(MutationObserver) ||
            // PhantomJS and iOS 7.x
            MutationObserver.toString() === '[object MutationObserverConstructor]'
          )) {
            // Use MutationObserver where native Promise is not available,
            // e.g. PhantomJS, iOS7, Android 4.4
            // (#6466 MutationObserver is unreliable in IE11)
            let counter = 1
            const observer = new MutationObserver(flushCallbacks)
            const textNode = document.createTextNode(String(counter))
            observer.observe(textNode, {
              characterDatatrue
            })
            timerFunc = () => {
              counter = (counter + 1) % 2
              textNode.data = String(counter)
            }
            isUsingMicroTask = true
          }
          復(fù)制代碼
          1. 對(duì)非IE瀏覽器和是否可以使用 HTML5 新特性 MutationObserver 進(jìn)行判斷。
          2. 實(shí)例一個(gè) MutationObserver 對(duì)象,這個(gè)對(duì)象主要是對(duì)瀏覽器 DOM 變化進(jìn)行監(jiān)聽,當(dāng)實(shí)例化 MutationObserver 對(duì)象并且執(zhí)行對(duì)象 observe,設(shè)置 DOM 節(jié)點(diǎn)發(fā)生改變時(shí)自動(dòng)觸發(fā)回調(diào)。
          3. timerFunc 賦值為一個(gè)改變 DOM 節(jié)點(diǎn)的方法,當(dāng) DOM 節(jié)點(diǎn)發(fā)生改變,觸發(fā) flushCallbacks 。(這里其實(shí)就是想用利用 MutationObserver 的特性進(jìn)行異步操作)

          setImmediate 分支

          else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
            // Fallback to setImmediate.
            // Technically it leverages the (macro) task queue,
            // but it is still a better choice than setTimeout.
            timerFunc = () => {
              setImmediate(flushCallbacks)
            }
          }
          復(fù)制代碼
          1. 判斷 setImmediate 是否存在,setImmediate 是高版本 IE (IE10+) 和 edge 才支持的。
          2. 如果存在,傳入 flushCallbacks 執(zhí)行 setImmediate

          setTimeout 分支

          else {
            // Fallback to setTimeout.
            timerFunc = () => {
              setTimeout(flushCallbacks, 0)
            }
          }
          復(fù)制代碼
          1. 當(dāng)以上所有分支異步 api 都不支持的時(shí)候,使用 macro task (宏任務(wù))的 setTimeout 執(zhí)行 flushCallbacks 。

          執(zhí)行降級(jí)

          我們可以發(fā)現(xiàn),給 timerFunc 賦值是一個(gè)降級(jí)的過程。為什么呢,因?yàn)?Vue 在執(zhí)行的過程中,執(zhí)行環(huán)境不同,所以要適配環(huán)境。

          未命名文件 (4).png

          這張圖便于我們更清晰的了解到降級(jí)的過程。

          flushCallbacks 函數(shù)

          function flushCallbacks () {
            pending = false
            const copies = callbacks.slice(0)
            callbacks.length = 0
            for (let i = 0; i < copies.length; i++) {
              copies[i]()
            }
          }
          復(fù)制代碼

          循環(huán)遍歷,按照 隊(duì)列 數(shù)據(jù)結(jié)構(gòu) “先進(jìn)先出” 的原則,逐一執(zhí)行所有 callback 。

          總結(jié)

          到這里就全部講完了,nextTick 的原理就是利用 Event loop 事件線程去異步重新渲染,分支判斷首要選擇 Promise 的原因是當(dāng)同步JS代碼執(zhí)行完畢,執(zhí)行棧清空會(huì)首先查看 micro task (微任務(wù))隊(duì)列是否為空,不為空首先執(zhí)行微任務(wù)。在我們 DOM 依賴數(shù)據(jù)發(fā)生變化的時(shí)候,會(huì)異步重新渲染 DOM ,但是比如像 echartscanvas……這些 Vue 無法在初始狀態(tài)下收集依賴的 DOM ,我們就需要手動(dòng)執(zhí)行 nextTick 方法使其重新渲染。

          聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。



          內(nèi)推社群


          我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 66
          點(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片找四虎公司 | 国产扒开腿精品无码高潮视频 | 中文字幕在线观看一区 | 曰本国电影黄色免看费 | 毛片学生姝 |