前言:目前來說,nextTick
我們遇到的比較少,至少對我來說是這樣的,但是有一些聰明的小朋友早早就注意到這個知識點了。nextTick
是前端開發(尤其是 Vue 生態)中的核心知識點,原理上跟Vue的異步更新有密切關系,對于面試者考察很有區分度,如果能回答的很好,對面試也是很有幫助的,所以我們有必要花費時間來學習一下。
一、nextTick是什么
我們來看看官方的定義:
在下次 DOM 更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM。
看完是不是有一堆問號?沒關系,我們來拆分一下關鍵詞
下次 DOM 更新循環結束之后
?
延遲回調
?
更新后的 DOM
?
1、 下次 DOM 更新循環結束之后
Vue更新DOM是有策略的,不是同步更新,是異步的,批量更新DOM,避免重繪導致的性能損耗(比如連續修改數據時僅觸發一次渲染)
如果你學過防抖和節流的話,有沒有感覺這里邏輯有點相像,他們都是前端性能優化手段,那么我們順便就來復習一下吧。
他們有著共同目標:減少高頻操作帶來的性能損耗。
實現邏輯上的對比:
機制 | Vue異步更新 | 防抖(Debounce) | 節流(Throttle) |
---|---|---|---|
觸發條件 | 數據變化 | 事件觸發(如resize) | 事件觸發(如scroll) |
執行策略 | 合并同一事件循環內的所有修改 | 延遲執行,僅保留最后一次操作 | 固定間隔執行一次 |
應用場景 | 虛擬DOM批量渲染 | 搜索框輸入聯想 | 滾動加載更多 |
核心區別:
Vue異步更新:
-
依賴瀏覽器事件循環
-
自動完成,開發者無需手動控制
防抖/節流:
-
需開發者顯式編碼實現
-
作用于業務邏輯層而非框架底層
呼~復習完了,我們進入正題
這個關鍵詞跟瀏覽器的事件循環有關,我們來簡單回顧一下:
瀏覽器事件循環
JS是一門單線程的語言,因為它運行在瀏覽器的渲染主線程中,而主線程只有一個。而渲染主線程承擔著許多的工作,渲染頁面、執行JS都在其中執行。如果使用同步的方式,極有可能導致主線程產生阻塞,從而導致消息隊列中的許多其他任務無法得到執行,所以瀏覽器采用異步的方式來避免。
?console.log('腳本開始') setTimeout(() => console.log('定時器'), 0) Promise.resolve().then(() => {console.log('Promise') setTimeout(() => console.log('嵌套定時器'), 0) })console.log('腳本結束') ???
??輸出:/* 輸出順序:腳本開始腳本結束Promise定時器嵌套定時器*/
2、
延遲回調
這里的"延遲"不是指setTimeout那樣的宏任務延遲,而是指將回調函數推入微任務隊列(microtask queue)等待執行。Vue會根據運行環境自動選擇最優的實現方式:
-
優先使用
Promise.then()
:-
如果當前環境支持
Promise
,Vue 會優先使用Promise.then()
來實現微任務。 -
這是現代瀏覽器中最高效的方式,因為它直接利用了 JavaScript 的微任務機制。
-
-
降級方案:
MutationObserver
:-
如果當前環境不支持
Promise
,但支持MutationObserver
,Vue 會使用MutationObserver
作為替代方案。 -
MutationObserver
的回調會被加入微任務隊列,雖然它主要用于監聽 DOM 變化,但也可以用來實現微任務的效果。
-
-
降級方案:
setImmediate
:-
如果當前環境不支持
Promise
和MutationObserver
,但支持setImmediate
,Vue 會使用setImmediate
。 -
setImmediate
是 Node.js 和 IE10+ 提供的一種機制,雖然它是宏任務,但它的執行時機比setTimeout(fn, 0)
更早。
-
-
兜底方案:
setTimeout(fn, 0)
:-
如果當前環境不支持
Promise
、MutationObserver
和setImmediate
,Vue 會使用setTimeout(fn, 0)
作為兜底方案。 -
雖然
setTimeout(fn, 0)
是宏任務,但它是最通用的實現方式,確保在當前同步代碼執行完畢后執行回調。
-
3、
更新后的 DOM
由于Vue的響應式更新是異步的,直接修改數據后立即訪問DOM獲取的是舊值。通過
nextTick
可以確保獲取到的是最新渲染結果:?<script setup>import { ref, nextTick } from 'vue';?const num = ref(0);?const changeValue = async () => {num.value = 1; // 更新數據console.log('第一次打印:', num.value); // 打印當前數據console.log('第一次 DOM:', document.querySelector('div').textContent); // 打印當前 DOM 內容?await nextTick(); // 等待 DOM 更新完成?console.log('第二次打印:', num.value); // 打印當前數據console.log('第二次 DOM:', document.querySelector('div').textContent); // 打印更新后的 DOM 內容};?</script><template><div>{{ num }}</div><button @click="changeValue">Change</button></template>?
打印結果:
-
-
第一次打印:1
-
第一次打印DOM:0
-
第二次打印:1
-
第二次打印DOM:1
-
總的來說,nextTick就是一個工具,用來確保在 Vue 更新完 DOM 之后,再執行某些操作。它特別有用,因為有時候你修改了數據,Vue 需要一點時間來更新 DOM,而
nextTick
可以讓你在 DOM 更新完成之后,立即執行你需要的操作。二、使用場景
1. DOM依賴型操作
-
獲取更新后的元素尺寸/位置
-
基于新DOM初始化第三方庫(如圖表庫)
<template><div><h1>Vue 3 nextTick 示例</h1><button @click="loadData">Load Data</button></div></template>?<script setup>import { ref, nextTick } from 'vue';?const dataLoaded = ref(false);?function loadData() {// 模擬數據加載dataLoaded.value = true;?// 確保數據已渲染到 DOMnextTick(() => {initChart();});}?function initChart() {console.log('Chart initialized');// 在這里初始化圖表}</script>
2. 組件通信
父子組件生命周期執行順序導致的問題:
<template><div><button @click="showChild">Show Child</button><ChildComponent v-if="isShow" ref="child" /></div></template>?<script setup>import { ref, nextTick } from 'vue';import ChildComponent from './ChildComponent.vue';?const isShow = ref(false);?function showChild() {isShow.value = true; // 顯示子組件nextTick(() => {const child = $refs.child; // 獲取子組件的引用if (child) {child.doSomething(); // 確保子組件已掛載并調用方法}});}</script>
3. 特殊交互處理
如自動聚焦輸入框:
?<template><div><button @click="showInputAndFocus">Show Input and Focus</button><input v-if="showInput" ref="input" /></div></template>?<script setup>import { ref, nextTick } from 'vue';?const showInput = ref(false);?function showInputAndFocus() {showInput.value = true; // 顯示輸入框nextTick(() => {const inputElement = $refs.input; // 獲取輸入框的引用if (inputElement) {inputElement.focus(); // 聚焦到輸入框}});}</script>
-
-
-
同步代碼執行完畢后,事件循環開始處理微任務隊列。
-
執行微任務隊列中的
Promise
回調,輸出Promise
。 -
在
Promise
回調中,setTimeout(() => console.log('嵌套定時器'), 0)
被加入宏任務隊列。 -
微任務隊列清空后,事件循環開始處理宏任務隊列。
-
執行第一個宏任務
setTimeout(() => console.log('定時器'), 0)
,輸出定時器
。 -
執行第二個宏任務
setTimeout(() => console.log('嵌套定時器'), 0)
,輸出嵌套定時器
。 -
執行順序:同步代碼 → 微任務隊列 → 宏任務隊列
-
同步代碼執行:首先執行當前的同步代碼。
-
清空微任務隊列:在同步代碼執行完畢后,事件循環會立即清空微任務隊列,依次執行微任務隊列中的所有任務。
-
執行宏任務:清空微任務隊列后,事件循環會從宏任務隊列中取出一個任務執行。
-
重復步驟 2 和 3:每次執行完一個宏任務后,事件循環會再次清空微任務隊列,然后繼續執行下一個宏任務。
-
宏任務:通常用于延遲執行、異步操作或與瀏覽器的 UI 渲染相關。它們的執行時機在事件循環的每次迭代中,每次只執行一個。(
setTimeout
和setInterval
setImmediate
I/O 操作 UI 渲染) -
微任務:通常用于需要在當前任務執行完畢后立即執行的回調。它們的優先級高于宏任務,會在每次宏任務執行完畢后立即清空。(
Promise
MutationObserver
queueMicrotask
) -
注意,這里為了方便理解,我們把任務籠統歸類為宏任務和微任務,現在對于宏任務和微任務已經有了更細的劃分,大家可以去了解一下,但是這里只是為了讓大家方便理解,采用了宏任務和微任務的說法,其實也是對的,但是這樣說比較舊
-
三、實戰測試
測試案例1:驗證執行順序
猜猜最后打印出什么結果
<template><div><h1>Vue 3 nextTick 示例</h1></div></template>?<script setup>import { onMounted, nextTick } from 'vue';?onMounted(() => {console.log('1');Promise.resolve().then(() => console.log('2'));nextTick(() => console.log('3'));setTimeout(() => console.log('4'), 0);});</script>
打印:1234
首先執行同步操作,打印1
執行微隊列的任務promise和nextTick,打印2和3
執行宏隊列任務,打印4
測試案例2:連續修改測試
猜猜最后打印出什么結果
?
<template><div><h1>Vue 3 nextTick 示例</h1><button @click="testBatchUpdate">Test Batch Update</button></div></template>?<script setup>import { ref, nextTick } from 'vue';?const count = ref(0);?function testBatchUpdate() {count.value = 1;nextTick(() => console.log(count.value));count.value = 2;nextTick(() => console.log(count.value));}</script>
打印:22
先執行同步操作,先后吧count的值改為1和2,然后執行微隊列的任務,打印兩次count的值
四、面試常問
整理了一些面視可能會問的問題
1. 核心原理
Q:nextTick的實現原理是什么? A:基于JavaScript事件循環機制,優先使用微任務隊列實現異步回調。Vue會維護一個回調隊列,在同一個tick中多次調用nextTick只會向隊列添加任務,最終通過異步API批量執行。
2. 執行時機
Q:nextTick與setTimeout(fn, 0)的區別? A:nextTick會優先使用微任務(執行早于setTimeout),且能自動選擇最優的異步方案。setTimeout屬于宏任務,執行時機更晚。
3. 與Vue響應式的關系
Q:為什么Vue選擇異步更新DOM? A:主要考慮兩點:
-
性能優化:合并同一事件循環中的所有數據變更
-
邏輯合理性:確保無論修改多少次數據,組件只更新一次
4. 特殊場景
Q:nextTick回調中再修改數據會怎樣? A:會進入新的更新周期,可能觸發額外的渲染。應避免這種嵌套使用。
Q:為什么 nextTick
回調中修改數據可能導致額外渲染? A:因為 nextTick
回調執行時,當前渲染周期已結束。若在回調中修改數據,會觸發新的渲染周期(類似“連鎖反應”)。
5. nextTick的常見使用場景有哪些
-
A:
1、確保 DOM 更新完成后執行某些操作(如獲取元素尺寸、滾動位置等)。
2、在子組件掛載后執行某些操作(如調用子組件的方法)。
3、在批量更新數據后,確保所有更新完成后再執行某些操作。
6. nextTick如何幫助優化性能
-
A:
-
通過確保在 DOM 更新完成后執行回調,
nextTick
可以避免在 DOM 更新過程中進行不必要的操作,從而減少性能開銷。上面只是一部分,多的大家可以自己下去搜索一些
-
-
五、致謝
bilibili 瀏覽器事件循環原理嗶哩嗶哩bilibili
官方網站 nextTick | Vue3
騰訊元寶 豆包 kimi
我自己 😎😋
愛學習的你們👏🌹