<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                Vue 和 React 都實現了異步更新策略。雖然實現的方式不盡相同,但都達到了減少 DOM 操作、避免過度渲染的目的。通過研究框架的運行機制,其設計思路將深化我們對 DOM 優化的理解,其實現手法將拓寬我們對 DOM 實踐的認知。 本節我們將基于 Event Loop 機制,對 Vue 的異步更新策略作探討。 ## 前置知識:Event Loop 中的“渲染時機” 搞懂 Event Loop,是理解 Vue 對 DOM 操作優化的第一步。 ### Micro-Task 與 Macro-Task 事件循環中的異步隊列有兩種:macro(宏任務)隊列和 micro(微任務)隊列。 常見的 macro-task 比如: setTimeout、setInterval、 setImmediate、script(整體代碼)、 I/O 操作、UI 渲染等。 常見的 micro-task 比如: process.nextTick、Promise、MutationObserver 等。 ### Event Loop 過程解析 基于對 micro 和 macro 的認知,我們來走一遍完整的事件循環過程。 一個完整的 Event Loop 過程,可以概括為以下階段: * 初始狀態:調用棧空。micro 隊列空,macro 隊列里有且只有一個 script 腳本(整體代碼)。 * 全局上下文(script 標簽)被推入調用棧,同步代碼執行。在執行的過程中,通過對一些接口的調用,可以產生新的 macro-task 與 micro-task,它們會分別被推入各自的任務隊列里。同步代碼執行完了,script 腳本會被移出 macro 隊列,**這個過程本質上是隊列的 macro-task 的執行和出隊的過程**。 * 上一步我們出隊的是一個 macro-task,這一步我們處理的是 micro-task。但需要注意的是:當 macro-task 出隊時,任務是**一個一個**執行的;而 micro-task 出隊時,任務是**一隊一隊**執行的(如下圖所示)。因此,我們處理 micro 隊列這一步,會逐個執行隊列中的任務并把它出隊,直到隊列被清空。 ![](https://user-gold-cdn.xitu.io/2018/10/1/1662fc9d8bf609a6?w=480&h=410&f=png&s=8715) * **執行渲染操作,更新界面**(敲黑板劃重點)。 * 檢查是否存在 Web worker 任務,如果有,則對其進行處理 。 (上述過程循環往復,直到兩個隊列都清空) 我們總結一下,每一次循環都是一個這樣的過程: ![](https://user-gold-cdn.xitu.io/2018/10/1/1662ff57ebe7a73f?w=857&h=243&f=png&s=28272) ### 渲染的時機 大家現在思考一個這樣的問題:假如我想要在異步任務里進行DOM更新,我該把它包裝成 micro 還是 macro 呢? 我們先假設它是一個 macro 任務,比如我在 script 腳本中用 setTimeout 來處理它: ``` // task是一個用于修改DOM的回調 setTimeout(task, 0) ``` 現在 task 被推入的 macro 隊列。但因為 script 腳本本身是一個 macro 任務,所以本次執行完 script 腳本之后,下一個步驟就要去處理 micro 隊列了,再往下就去執行了一次 render,對不對? 但本次render我的目標task其實并沒有執行,想要修改的DOM也沒有修改,因此這一次的render其實是一次無效的render。 macro 不 ok,我們轉向 micro 試試看。我用 Promise 來把 task 包裝成是一個 micro 任務: ``` Promise.resolve().then(task) ``` 那么我們結束了對 script 腳本的執行,是不是緊接著就去處理 micro-task 隊列了?micro-task 處理完,DOM 修改好了,緊接著就可以走 render 流程了——不需要再消耗多余的一次渲染,不需要再等待一輪事件循環,直接為用戶呈現最即時的更新結果。 因此,我們更新 DOM 的時間點,應該盡可能靠近渲染的時機。**當我們需要在異步任務中實現 DOM 修改時,把它包裝成 micro 任務是相對明智的選擇**。 ## 生產實踐:異步更新策略——以 Vue 為例 什么是異步更新? 當我們使用 Vue 或 React 提供的接口去更新數據時,這個更新并不會立即生效,而是會被推入到一個隊列里。待到適當的時機,隊列中的更新任務會被**批量觸發**。這就是異步更新。 異步更新可以幫助我們避免過度渲染,是我們上節提到的“讓 JS 為 DOM 分壓”的典范之一。 ### 異步更新的優越性 異步更新的特性在于它**只看結果**,因此渲染引擎**不需要為過程買單**。 最典型的例子,比如有時我們會遇到這樣的情況: ``` // 任務一 this.content = '第一次測試' // 任務二 this.content = '第二次測試' // 任務三 this.content = '第三次測試' ``` 我們在三個更新任務中對同一個狀態修改了三次,如果我們采取傳統的同步更新策略,那么就要操作三次 DOM。但本質上需要呈現給用戶的目標內容其實只是第三次的結果,也就是說只有第三次的操作是有意義的——我們白白浪費了兩次計算。 但如果我們把這三個任務塞進異步更新隊列里,它們會先在 JS 的層面上被**批量執行完畢**。當流程走到渲染這一步時,它僅僅需要針對有意義的計算結果操作一次 DOM——這就是異步更新的妙處。 ### Vue狀態更新手法:nextTick Vue 每次想要更新一個狀態的時候,會先把它這個更新操作給包裝成一個異步操作派發出去。這件事情,在源碼中是由一個叫做 nextTick 的函數來完成的: ``` export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // 檢查上一個異步任務隊列(即名為callbacks的任務數組)是否派發和執行完畢了。pending此處相當于一個鎖 if (!pending) { // 若上一個異步任務隊列已經執行完畢,則將pending設定為true(把鎖鎖上) pending = true // 是否要求一定要派發為macro任務 if (useMacroTask) { macroTimerFunc() } else { // 如果不說明一定要macro 你們就全都是micro microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } ``` 我們看到,Vue 的異步任務默認情況下都是用 Promise 來包裝的,也就是是說它們都是 micro-task。這一點和我們“前置知識”中的渲染時機的分析不謀而合。 為了帶大家熟悉一下常見的 macro 和 micro 派發方式、加深對 Event Loop 的理解,我們繼續細化解析一下 macroTimeFunc() 和 microTimeFunc() 兩個方法。 macroTimeFunc() 是這么實現的: ``` // macro首選setImmediate 這個兼容性最差 if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { // 兼容性最好的派發方式是setTimeout macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } ``` microTimeFunc() 是這么實現的: ``` // 簡單粗暴 不是ios全都給我去Promise 如果不兼容promise 那么你只能將就一下變成macro了 if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { 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) } } else { // 如果無法派發micro,就退而求其次派發為macro microTimerFunc = macroTimerFunc } ``` 我們注意到,無論是派發 macro 任務還是派發 micro 任務,派發的任務對象都是一個叫做 flushCallbacks 的東西,這個東西做了什么呢? flushCallbacks 源碼如下: ``` function flushCallbacks () { pending = false // callbacks在nextick中出現過 它是任務數組(隊列) const copies = callbacks.slice(0) callbacks.length = 0 // 將callbacks中的任務逐個取出執行 for (let i = 0; i < copies.length; i++) { copies[i]() } } ``` 現在我們理清楚了:Vue 中每產生一個狀態更新任務,它就會被塞進一個叫 callbacks 的數組(此處是任務隊列的實現形式)中。這個任務隊列在被丟進 micro 或 macro 隊列之前,會先去檢查當前是否有異步更新任務正在執行(即檢查 pending 鎖)。如果確認 pending 鎖是開著的(false),就把它設置為鎖上(true),然后對當前 callbacks 數組的任務進行派發(丟進 micro 或 macro 隊列)和執行。設置 pending 鎖的意義在于保證狀態更新任務的有序進行,避免發生混亂。 本小節我們從性能優化的角度出發,通過解析Vue源碼,對異步更新這一高效的 DOM 優化手段有了感性的認知。同時幫助大家進一步熟悉了 micro 與 macro 在生產中的應用,加深了對 Event Loop 的理解。事實上,Vue 源碼中還有許多值得稱道的生產實踐,其設計模式與編碼細節都值得我們去細細品味。對這個話題感興趣的同學,課后不妨移步 [Vue運行機制解析](https://juejin.im/book/5a36661851882538e2259c0f) 進行探索。 ## 小結 至此,我們的 DOM 優化之路才走完了一半。 以上我們都在討論“如何減少 DOM 操作”的話題。這個話題比較宏觀——DOM 操作也分很多種,它們帶來的變化各不相同。有的操作只觸發重繪,這時我們的性能損耗就小一些;有的操作會觸發回流,這時我們更“肉疼”一些。那么如何理解回流與重繪,如何借助這些理解去提升頁面渲染效率呢? 結束了 JS 的征程,我們下面就走進 CSS 的世界一窺究竟。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看