vue nextTick深入理解-vue性能優化、DOM更新時機、事件循環機制


一、定義[nextTick、事件循環]

   nextTick的由來:
    由於VUE的數據驅動視圖更新,是異步的,即修改數據的當下,視圖不會立刻更新,而是等同一事件循環中的所有數據變化完成之后,再統一進行視圖更新。
   nextTick的觸發時機:
    在同一事件循環中的數據變化后,DOM完成更新,立即執行nextTick(callback)內的回調。
   應用場景:
    需要在視圖更新之后,基於新的視圖進行操作。
  以上出現了事件循環的概念,其涉及到JS的運行機制,包括主線程的執行棧、異步隊列、異步API、事件循環的協作,此處不展開之后再總結。大致理解:主線程完成同步環境執行,查詢任務隊列,提取隊首的任務,放入主線程中執行;執行完畢,再重復該操作,該過程稱為事件循環。而主線程的每次讀取任務隊列操作,是一個事件循環的開始。異步callback不可能處在同一事件循環中。
   簡單總結事件循環:
    同步代碼執行 -> 查找異步隊列,推入執行棧,執行callback1[事件循環1] ->查找異步隊列,推入執行棧,執行callback2[事件循環2]...
  即每個異步callback,最終都會形成自己獨立的一個事件循環。
  結合nextTick的由來,可以推出每個事件循環中,nextTick觸發的時機:
     同一事件循環中的代碼執行完畢 -> DOM 更新 -> nextTick callback觸發
   tips:本文的任務隊列、消息隊列、異步隊列指同一個東西,均指macrotask queue。
        事件循環詳解:http://www.cnblogs.com/hity-tt/p/6733062.html 

二、實例理解nextTick的使用,並給出在頁面渲染上的優化巧用

  (tips:代碼的正確閱讀方式:看template組成、跳過script代碼、看代碼后面的用例設計、看之后的代碼分析、同時結合回頭結合script代碼理解)
<template>
    <div>
        <ul>
            <li v-for="item in list1">{{item}}</li>
        </ul>
        <ul>
            <li v-for="item in list2">{{item}}</li>
        </ul>
        <ol>
            <li v-for="item in list3">{{item}}</li>
        </ol>
        <ol>
            <li v-for="item in list4">{{item}}</li>
        </ol>
        <ol>
            <li v-for="item in list5">{{item}}</li>
        </ol>
    </div>
</template>
<script type="text/javascript">
export default {
    data() {
        return {
            list1: [],
            list2: [],
            list3: [],
            list4: [],
            list5: []
        }
    },
    created() {
        this.composeList12()
        this.composeList34()
        this.composeList5()
        this.$nextTick(function() {
            // DOM 更新了
            console.log('finished test ' + new Date().toString())             console.log(document.querySelectorAll('li').length)
        })
    },
    methods: {
        composeList12() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list1, i, 'I am a 測試信息~~啦啦啦' + i)
            }
            console.log('finished list1 ' + new Date().toString())

            for (let i = 0; i < count; i++) {
                Vue.set(me.list2, i, 'I am a 測試信息~~啦啦啦' + i)
            }
            console.log('finished list2 ' + new Date().toString())

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick1&2 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        },
        composeList34() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list3, i, 'I am a 測試信息~~啦啦啦' + i)
            }
            console.log('finished list3 ' + new Date().toString())

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick3 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })

            setTimeout(me.setTimeout1, 0)
        },
        setTimeout1() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list4, i, 'I am a 測試信息~~啦啦啦' + i)
            }
            console.log('finished list4 ' + new Date().toString())

            me.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick4 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        },
        composeList5() {
            let me = this
            let count = 10000

            this.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick5-1 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })

            setTimeout(me.setTimeout2, 0)
        },
        setTimeout2() {
            let me = this
            let count = 10000

            for (let i = 0; i < count; i++) {
                Vue.set(me.list5, i, 'I am a 測試信息~~啦啦啦' + i)
            }
            console.log('finished list5 ' + new Date().toString())

            me.$nextTick(function() {
                // DOM 更新了
                console.log('finished tick5 ' + new Date().toString())
                console.log(document.querySelectorAll('li').length)
            })
        }
    }
}
</script>

   2.1、用例設計
    用例1:通過list1、2、3驗證,處在同步代碼中的DOM更新情況及nextTick的觸發時機;
    用例2:通過list3、list4驗證,同步代碼及異步代碼中Dom更新及nextTick觸發的區別;
    用例3:通過list4、list5對比驗證,多個異步代碼中nextTick觸發的區別;
    用例4:通過在視圖更新后獲取DOM中<li>的數量,判斷nextTick序列渲染的時間點。
 
   2.2、代碼分析
    函數執行步驟:
      事件循環1:
        step1: this.composeList12() -> update list1, update list2 -> 綁定tick’1&2’
        step2: this.composeList34() -> update list3, 設置異步1setTimeout1 -> 綁定tick’3’
        step3: this.composeList5() -> 綁定tick’5-1’ -> 設置異步2setTimeout2 
        step4: 綁定tick’test’
      事件循環2:
        將setTimeout1的callback推入執行棧 -> update list4 -> 綁定tick’4’
      事件循環3:
        將setTimeout2的callback推入執行棧 -> update list5 -> 綁定tick’5’
 
   2.3、推斷輸出消息
    由於同一事件循環中的tick按執行順序,因此消息輸出為即:
      [同步環境]update list1 -> update list2 -> update list3 -> tick‘1&2’ -> tick‘3’ -> tick’5-1’ -> tick’test'
      [事件循環1]->update list4 -> tick’4’ 
       [事件循環2] ->update list5 -> tick’5’
 
   2.4、實際運行結果如下圖
           
    該demo中,設置了5個size為10000的數組,從而能從時間及消息輸出兩個維度來了解nextTick的執行情況。另外,額外增加了一個參數,即更新后的視圖中<li>的數量,從這個數量,可以考察出同一事件循環中的nextTick執行情況。由運行結果圖可以看出實際的輸出與推導的輸出結果相符合。
 
   2.5、總結
    從用例1得出:
      a、在同一事件循環中,只有所有的數據更新完畢,才會調用nextTick;
      b、僅在同步執行環境數據完全更新完畢,DOM才開始渲染,頁面才開始展現;
      c、在同一事件循環中,如果存在多個nextTick,將會按最初的執行順序進行調用;
    從用例1+用例4得出:
      d、從同步執行環境中的四個tick對應的‘li’數量均為30000可看出,同一事件循環中,nextTick所在的視圖是相同的;
    從用例2得出:
      e、只有同步環境執行完畢,DOM渲染完畢之后,才會處理異步callback
    從用例3得出:
      f、每個異步callback最后都會處在一個獨立的事件循環中,對應自己獨立的nextTick;
    從用例1結論中可得出:
      g、這個事件環境中的數據變化完成,在進行渲染[視圖更新],可以避免DOM的頻繁變動,從而避免了因此帶來的瀏覽器卡頓,大幅度提升性能;
    從b可以得出:
      h、在首屏渲染、用戶交互過程中,要巧用同步環境及異步環境;首屏展現的內容,盡量保證在同步環境中完成;其他內容,拆分到異步中,從而保證性能、體驗。
    
  tips:
    1、可產生異步callback的有:promise(microtask queue)、setTimeout、MutationObserver、DOM事件、Ajax等;
    2、 vue DOM的視圖更新實現,,使用到了ES6的Promise及HTML5的 MutationObserver,當環境不支持時,使用setTimeout(fn, 0)替代。上述的三種方法,均為異步API。其中MutationObserver類似事件,又有所區別;事件是同步觸發,其為異步觸發,即DOM發生變化之后,不會立刻觸發,等當前所有的DOM操作都結束后觸發。關於異步API、事件循環將在以后補充。
 
     事件循環、任務隊列詳解:http://www.cnblogs.com/hity-tt/p/6733062.html 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM