帶你深入瞭解下this.$nextTick!

2022-12-21 22:00:41

都快2023年,你還不會this.$nextTick嗎,Vue2都出快10年了,Vue3也已經出了兩年多了,沒錯!說出來就丟臉,我現在才會this.nextTick(實話實說),好的咱先百度一下,噠噠噠....網速飛快地跳到了Vue.js官網檔案,突然發現檔案中有一言?:

nextTick:在下一次DOM更新迴圈結束之後執行延遲迴撥。在修改資料之後立即使用這個方法,獲取更新後的DOM。它有兩個引數:第一個引數是回撥函數,不傳時提供promise呼叫;第二個引數是執行環境上下文,不傳預設是自動繫結到呼叫它的範例上。【相關推薦:、】

我們先看看nextTick究竟是個啥?

console.log(this.$nextTick);
// 控制檯列印
if(fn){
  return nextTick(fn, this);
}
登入後複製

我們可以看出nextTick就是一個方法,方法有兩個引數:fn和this,fn就是需要傳的回撥函數,this就是所說的執行環境上下文。那麼問題來了,在Vue中是如何實現在下一次DOM更新結束之後才會執行延遲迴撥的? 我們先看看下面的例子:

<div ref="test1">created:{{message}}</div>
// vue範例
data: {
    message: "Hello World!",
},
created(){
    this.message = '你好,世界!';
    console.log(this.$refs.test1.innerText);// 報錯
    // TypeError: Cannot read properties of undefined (reading 'innerText')
    this.$nextTick(()=>{
         console.log('test1 nextTick:',this.$refs.test1.innerText);// 你好,世界!
    });
},
登入後複製

從上面例子中,在created生命週期中操作了DOM,但是我們都知道created生命週期只是初始化了資料,這期間是還沒有渲染DOM的,如果我們直接操作DOM是找不到DOM元素的,那麼問題來了:為什麼放在nextTick中就可以獲取到了DOM元素呢? 這不是很明顯嗎,等到DOM渲染完才呼叫不就獲取到了嗎,從而知道了nextTick作用就是用來等下次DOM渲染完才去呼叫nextTick內的DOM操作程式碼。那麼問題又來了,nextTick究竟做了什麼?下面我們一起從原始碼層面來分別分析Vue2和Vue3版本的nextTick原理是啥。

Vue2版本的nextTick

由於Vue暴露給開發者的是nextTick這個方法,在這個方法中主要做了三件事,回撥函數的新增延遲執行回撥函數判斷當前的nextTick是否傳入回撥函數。不傳的話,是一個Promise,this.$nextTick.then(()=>{}),按Promise處理。

image.png

  • 回撥函數新增入callbacks陣列,因為可能有多個nextTick函數在當前作用域中。

image.png

  • 判斷當前nextTick是否已經標記為pending=true,也就是正在執行,如果不是就執行timerFunc(非同步執行函數 用於非同步延遲呼叫 flushCallbacks 函數)。timerFunc的執行,判斷當前環境是否支援promise、MutationObserver、setImmediate、setTimeout,優先順序高低從前到後,分四種情況:
  • 優先使用Promise,如果當前環境支援promise,nextTick預設優先使用promise去執行延遲迴撥函數,timerFunc執行的是Promise,promise是es6下的語法,如果當前環境只支援es6以語法下的,只能考慮後面支援情況了。

image.png

  • 支援MutationObserver,HTML5的api,中文意思是:修改的監聽,MutationObserver用來監聽DOM的變動,比如節點的增減、屬性的變動、文字內容的修改等都會觸發MutationObserver事件。注意地,與事件不同,事件是同步觸發,DOM的變動會立即觸發事件,而MutationObserver事件是非同步觸發,DOM不會立即觸發,需要等當前所有DOM操作完畢才會觸發。

MutationObserver有7個屬性:childList(true,監聽子節點的變動)、attributes(true,監聽屬性的變動)、characterData(true,監聽節點內容或節點文字的變動)、subtree(是否應用於該節點的所有後代節點)、attributeOldValue(觀察attributes變動時,是否需要記錄變動前的屬性值)、characterDataOldValue(觀察characterData變動時,是否需要記錄變動前的值)、attributeFilter(陣列,表示需要觀察的特定屬性(比如[‘class’,‘src’])。

為什麼需要建立一個文位元組點?因為在這裡操作DOM保證瀏覽器頁面是最新DOM渲染的,雖然看來好像是沒什麼作用,但這是保證拿到的DOM是最新的。

image.png

  • 支援setImmediatesetTimeout,setImmediate即時計時器立即執行工作,它是在事件輪詢之後執行,為了防止輪詢阻塞,每次只會呼叫一個。setTimeout按照一定時間後執行回撥函數。

image.png

好了好了,到了現在,我們都知道nextTick做了什麼吧,但是我們有沒有想過這樣的一個問題:既然都是非同步回撥執行等待DOM更新後才去呼叫操作DOM的程式碼,那麼這個機制又是什麼原理?這就是JS的執行機制有關了,涉及宏任務與微任務的知識,我們先來看看這樣的一道題:

console.log('同步程式碼1');
setTimeout(function () {
    console.log("setTimeout");
}, 0);
new Promise((resolve) => {
    console.log('同步程式碼2')
    resolve()
}).then(() => {
    console.log('promise.then')
})
console.log('同步程式碼3');
登入後複製

我們可能會問上面的輸出是個啥,首先js是單執行緒,所以在js程式執行中只有一個執行棧,實現不了多執行緒,所以就需要任務均衡分配,通俗的講,按任務急優先處理原則,js中分為同步任務和非同步任務,非同步任務又分為宏任務和微任務,同步任務先入棧,程式會先把執行棧中的所有同步任務執行完,再去判斷是否有非同步任務,而非同步任務中微任務的優先順序高於宏任務。如果當前執行棧為空,而微任務佇列不為空,就先執行微任務,等把所有微任務執行完,最後才會考慮宏任務。而上面程式碼中Promise是屬於微任務,而setTimeout是宏任務,所以上面的輸出為:

// 同步程式碼1
// 同步程式碼2
// 同步程式碼3
// promise.then 
// setTimeout
登入後複製

使用Vue2的nextTick

  • 傳入回撥函數引數使用:

    this.$nextTick(()=>{
      // ...操作DOM的程式碼
    })
    登入後複製
  • 不傳入回撥函數引數使用:

    // 方式一
    this.$nextTick().then(()=>{
      // ...操作DOM的程式碼
    })
    
    // 方式二
    await this.$nextTick();
    // 後寫操作DOM的程式碼
    登入後複製

Vue3版本的nextTick

Vue3版本就沒有Vue2版本的那麼多環境支援,nextTick封裝成了一個Promise非同步回撥函數執行。

// Vue3.2.45
// core-main\core-main\packages\runtime-core\src
export function nextTick<T = void>(
  this: T,
  fn?: (this: T) => void
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}
登入後複製

使用Vue3的nextTick

  • 傳入回撥函數使用

    import { nextTick } from 'vue' // 引入
    setup () {    
    nextTick(()=>{
       // ...操作DOM的程式碼
    })
    登入後複製
  • 不傳入回撥函數的使用

    import { nextTick } from 'vue' // 引入
    setup () {    
        // 方式一
        nextTick().then(()=>{
          // ...操作DOM的程式碼
        })
    
        // 方式二
        await nextTick();
        // 後寫操作DOM的程式碼
    }
    登入後複製

總結

  • nextTick可以通俗的當作一個Promise,所以nextTick屬於微任務。
  • nextTick在頁面更新資料後,DOM更新,可以通俗理解為,nextTick就是用來支援操作DOM的程式碼及時更新渲染頁面。也就是在資料變化後要執行的某個操作,而這個操作需要使用隨資料改變而改變的DOM結構的時候,這個操作都應該放進Vue.nextTick()的回撥函數中。
  • 在Vue生命週期的created()勾點函數進行的DOM操作一定要放在Vue.nextTick()的回撥函數中。

(學習視訊分享:、)

以上就是帶你深入瞭解下this.$nextTick!的詳細內容,更多請關注TW511.COM其它相關文章!