【轉載】Vue.nextTick 的原理和用途

 

對於 Vue.nextTick 方法,本身有些疑惑。在查詢了各類資料後,總結了一下其原理和用途,若有錯誤,請不吝賜教。 

概覽

官方文檔說明:
  • 用法:

在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。javascript

疑問:
  1. DOM 更新循環是指什麼?
  2. 下次更新循環是何時?
  3. 修改數據以後使用,是加快了數據更新進度嗎?
  4. 在什麼狀況下要用到?

原理

異步說明

Vue 實現響應式並 不是數據發生變化以後 DOM 當即變化,而是按必定的策略進行 DOM 的更新。

在 Vue 的文檔中,說明 Vue 是異步執行 DOM 更新的。關於異步的解析,能夠查看阮一峯老師的這篇文章。截取關鍵部分以下:css

具體來講,異步執行的運行機制以下。html

(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。

下圖就是主線程和任務隊列的示意圖。vue

 

事件循環說明

簡單來講,Vue 在修改數據後,視圖不會馬上更新,而是等同一事件循環中的全部數據變化完成以後,再統一進行視圖更新。java

知乎上的例子:react

//改變數據 vm.message = 'changed' //想要當即使用更新後的DOM。這樣不行,由於設置message後DOM尚未更新 console.log(vm.$el.textContent) // 並不會獲得'changed' //這樣能夠,nextTick裏面的代碼會在DOM更新後執行 Vue.nextTick(function(){ console.log(vm.$el.textContent) //能夠獲得'changed' }) 

圖解:web

 

事件循環:

第一個 tick(圖例中第一個步驟,即'本次更新循環'):ajax

  1. 首先修改數據,這是同步任務。同一事件循環的全部的同步任務都在主線程上執行,造成一個執行棧,此時還未涉及 DOM 。
  2. Vue 開啓一個異步隊列,並緩衝在此事件循環中發生的全部數據改變。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。

第二個 tick(圖例中第二個步驟,即'下次更新循環'):segmentfault

同步任務執行完畢,開始執行異步 watcher 隊列的任務,更新 DOM 。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和 MessageChannel 方法,若是執行環境不支持,會採用 setTimeout(fn, 0) 代替。性能優化

第三個 tick(圖例中第三個步驟):

此時就是文檔所說的

下次 DOM 更新循環結束以後

此時經過 Vue.nextTick 獲取到改變後的 DOM 。經過 setTimeout(fn, 0) 也能夠一樣獲取到。


簡單總結事件循環:

同步代碼執行 -> 查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環1] ->查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環2]...

總之,異步是單獨的一個tick,不會和同步在一個 tick 裏發生,也是 DOM 不會立刻改變的緣由。

對於事件循環,能夠在這裏查看更詳細的內容:  https://segmentfault.com/a/11...

用途

應用場景:須要在視圖更新以後,基於新的視圖進行操做。

created、mounted

須要注意的是,在 created 和 mounted 階段,若是須要操做渲染後的試圖,也要使用 nextTick 方法。

官方文檔說明:

注意 mounted 不會承諾全部的子組件也都一塊兒被掛載。若是你但願等到整個視圖都渲染完畢,能夠用 vm.$nextTick 替換掉 mounted
mounted: function () { this.$nextTick(function () { // Code that will run only after the
    // entire view has been rendered
 }) }

 

其餘應用場景

其餘應用場景以下三例:

例子1:

點擊按鈕顯示本來以 v-show = false 隱藏起來的輸入框,並獲取焦點。

showsou(){ this.showit = true //修改 v-show
  document.getElementById("keywords").focus()  //在第一個 tick 裏,獲取不到輸入框,天然也獲取不到焦點
}

 

修改成:

showsou(){ this.showit = true
  this.$nextTick(function () { // DOM 更新了
    document.getElementById("keywords").focus() }) }

 

例子2:

點擊獲取元素寬度。

<div id="app">
    <p ref="myWidth" v-if="showMe">{{ message }}</p>
    <button @click="getMyWidth">獲取p元素寬度</button>
</div>
 getMyWidth() { this.showMe = true; //this.message = this.$refs.myWidth.offsetWidth;
    //報錯 TypeError: this.$refs.myWidth is undefined
    this.$nextTick(()=>{ //dom元素更新後執行,此時能拿到p元素的屬性
        this.message = this.$refs.myWidth.offsetWidth; }) }

 

例子3:

使用 swiper 插件經過 ajax 請求圖片後的滑動問題。

實例理解 nextTick 應用

下面的例子來自 https://www.cnblogs.com/hity-..., 稍有改動。各位能夠複製運行一遍,加深理解。

<template>
    <div>
        <ul>
            <li class="example" v-for="item in list1">{{item}}</li>
        </ul>
        <ul>
            <li class="example" v-for="item in list2">{{item}}</li>
        </ul>
        <ol>
            <li class="example" v-for="item in list3">{{item}}</li>
        </ol>
        <ol>
            <li class="example" v-for="item in list4">{{item}}</li>
        </ol>
        <ol>
            <li class="example" 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(),document.querySelectorAll('.example').length) }) }, methods: { composeList12() { let me = this let count = 10000

            for (let i = 0; i < count; i++) { this.$set(me.list1, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list1 ' + new Date().toString(),document.querySelectorAll('.example').length) for (let i = 0; i < count; i++) { this.$set(me.list2, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list2 ' + new Date().toString(),document.querySelectorAll('.example').length) this.$nextTick(function() { // DOM 更新了
                console.log('finished tick1&2 ' + new Date().toString(),document.querySelectorAll('.example').length) }) }, composeList34() { let me = this let count = 10000

            for (let i = 0; i < count; i++) { this.$set(me.list3, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list3 ' + new Date().toString(),document.querySelectorAll('.example').length) this.$nextTick(function() { // DOM 更新了
                console.log('finished tick3 ' + new Date().toString(),document.querySelectorAll('.example').length) }) setTimeout(me.setTimeout1, 0) }, setTimeout1() { let me = this let count = 10000

            for (let i = 0; i < count; i++) { this.$set(me.list4, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list4 ' + new Date().toString(),document.querySelectorAll('.example').length) me.$nextTick(function() { // DOM 更新了
                console.log('finished tick4 ' + new Date().toString(),document.querySelectorAll('.example').length) }) }, composeList5() { let me = this let count = 10000

            this.$nextTick(function() { // DOM 更新了
                console.log('finished tick5-1 ' + new Date().toString(),document.querySelectorAll('.example').length) }) setTimeout(me.setTimeout2, 0) }, setTimeout2() { let me = this let count = 10000

            for (let i = 0; i < count; i++) { this.$set(me.list5, i, 'I am a 測試信息~~啦啦啦' + i) } console.log('finished list5 ' + new Date().toString(),document.querySelectorAll('.example').length) me.$nextTick(function() { // DOM 更新了
                console.log('finished tick5 ' + new Date().toString(),document.querySelectorAll('.example').length) }) } } } </script>

 

 

結果:

 

 

參考文章

vue nextTick深刻理解-vue性能優化、DOM更新時機、事件循環機制;
JavaScript 運行機制詳解:再談Event Loop
知乎:vue.js$nextTick的一個問題
JS 事件循環機制 - 任務隊列、web API、JS主線程的相互協同

相關文章
相關標籤/搜索