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://blog.csdn.net/one_girl/article/details/78790977javascript

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

tips:代碼的正確閱讀方式 看template組成、跳過script代碼、看代碼後面的用例設計、看以後的代碼分析、同時回頭結合script代碼理解html

<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:經過list一、二、3驗證,處在同步代碼中的DOM更新狀況及nextTick的觸發時機;
    用例2:經過list三、list4驗證,同步代碼及異步代碼中Dom更新及nextTick觸發的區別;
    用例3:經過list四、list5對比驗證,多個異步代碼中nextTick觸發的區別;
    用例4:經過在視圖更新後獲取DOM中li的數量,判斷nextTick序列渲染的時間點。vue

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

2.三、推斷輸出消息

    因爲同一事件循環中的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.四、實際運行結果以下圖

這裏寫圖片描述
該demo中設置了5個size爲10000的數組,從而能從時間及消息輸出兩個維度來了解nextTick的執行狀況。另外,額外增長了一個參數,即更新後的視圖中的li數量,從這個數量能夠考察出同一事件循環中的nextTick執行狀況。由運行結果圖能夠看出實際的輸出與推導的輸出結果相符合。promise

2.五、總結

從用例1得出:
一、在同一事件循環中,只有全部的數據更新完畢,纔會調用nextTick
二、 僅在同步執行環境數據更新完畢,DOM纔開始渲染,頁面纔開始展示。
三、在同一事件循環中,若是存在多個nextTick,將會按照最初的執行順序調用。
從用例1-4得出:
一、從同步執行環境中的四個tick對應的li數量均爲30000可看出,同一事件循環中,nextTick所在的視圖是相同的。
從用例2得出:
一、只有同步環境執行完畢、DOM渲染完畢,纔會處理異步callback
從用例3得出:
一、每一個異步callback最後都會處在一個獨立的事件循環中,對應本身獨立的nextTick瀏覽器

巧用:
從用例1結論中可得出:
這個事件環境中的數據變化完成,再進行渲染[視圖更新],能夠避免DOM的頻繁變更,從而避免了所以帶來的瀏覽器卡頓,大幅度提高性能。
在首屏渲染、用戶交互的過程當中,要巧用同步環境及異步環境;首屏展示的內容,儘可能保證在同步環境中完成;其餘內容,拆分到異步中,從而保證性能、體驗。異步

tips:
可產生異步callback的有:promise(microtask queue)、setTimeout、MutationObserver、DOM事件、Ajax等
vue DOM的視圖更新實現,使用到了ES6的Promise及HTML5的MutationObserver,當環境不支持時,使用setTimeout(fn,0)替代。上述的三種方法,均爲異步API。其中MutationObserver相似事件,又有所區別;事件時同步觸發的,其爲異步觸發,即DOM發生變化以後,不會馬上觸發,等當前全部的DOM操做都結束後觸發。函數

事件循環、任務隊列詳解:http://blog.csdn.net/one_girl/article/details/78790977性能

原文連接 http://www.cnblogs.com/hity-tt/p/6729118.html

相關文章
相關標籤/搜索