開始以前先看下官方對其的定義html
定義: 在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOMvue
看完是否是有一堆問號?咱們從中找出來產生問號的關鍵詞react
- 下次 DOM 更新循環結束以後?
- 執行延遲迴調?
- 更新後的 DOM?
從上面三個疑問大膽猜測一下性能優化
- vue 更新DOM是有策略的,不是同步更新
- nextTick 能夠接收一個函數作爲入參
- nextTick 後能拿到最新的數據
好了,問題都拋出來了,先來看一下如何使用多線程
import { createApp, nextTick } from 'vue' const app = createApp({ setup() { const message = ref('Hello!') const changeMessage = async newMessage => { message.value = newMessage // 這裏獲取DOM的value是舊值 await nextTick() // nextTick 後獲取DOM的value是更新後的值 console.log('Now DOM is updated') } } })
<a href="https://vue3js.cn/run/nextTick" target="_blank">親自試一試</a>app
那麼 nextTick
是怎麼作到的呢?爲了後面的內容更好理解,這裏咱們得從 js
的執行機制提及異步
JS執行機制
咱們都知道 JS
是單線程語言,即指某一時間內只能幹一件事,有的同窗可能會問,爲何 JS
不能是多線程呢?多線程就能同一時間內幹多件事情了async
是否多線程這個取決於語言的用途,一個很簡單的例子,若是同一時間,一個添加了 DOM
,一個刪除了 DOM
, 這個時候語言就不知道是該添仍是該刪了,因此從應用場景來看 JS
只能是單線程函數
單線程就意味着咱們全部的任務都須要排隊,後面的任務必須等待前面的任務完成才能執行,若是前面的任務耗時很長,一些從用戶角度上不須要等待的任務就會一直等待,這個從體驗角度上來說是不可接受的,因此JS
中就出現了異步的概念post
概念
- 同步 在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務
- 異步 不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行
運行機制
-
(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
-
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
-
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
-
(4)主線程不斷重複上面的第三步
nextTick
如今咱們回來vue
中的nextTick
實現很簡單,徹底是基於語言執行機制實現,直接建立一個異步任務,那麼nextTick
天然就達到在同步任務後執行的目的
const p = Promise.resolve() export function nextTick(fn?: () => void): Promise<void> { return fn ? p.then(fn) : p }
<a href="https://vue3js.cn/run/nextTick-demo-1.html" target="_blank">親自試一試</a>
看到這裏,有的同窗可能又會問,前面咱們猜測的 DOM
更新也是異步任務,那他們的這個執行順序如何保證呢?
別急,在源碼中nextTick
還有幾個兄弟函數,咱們接着往下看
queueJob and queuePostFlushCb
queueJob
維護job列隊,有去重邏輯,保證任務的惟一性,每次調用去執行 queueFlush
queuePostFlushCb
維護cb列隊,被調用的時候去重,每次調用去執行 queueFlush
const queue: (Job | null)[] = [] export function queueJob(job: Job) { // 去重 if (!queue.includes(job)) { queue.push(job) queueFlush() } } export function queuePostFlushCb(cb: Function | Function[]) { if (!isArray(cb)) { postFlushCbs.push(cb) } else { postFlushCbs.push(...cb) } queueFlush() }
queueFlush
開啓異步任務(nextTick)處理 flushJobs
function queueFlush() { // 避免重複調用flushJobs if (!isFlushing && !isFlushPending) { isFlushPending = true nextTick(flushJobs) } }
flushJobs
處理列隊,先對列隊進行排序,執行queue
中的job
,處理完後再處理postFlushCbs
, 若是隊列沒有被清空會遞歸調用flushJobs
清空隊列
function flushJobs(seen?: CountMap) { isFlushPending = false isFlushing = true let job if (__DEV__) { seen = seen || new Map() } // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child so its render effect will have smaller // priority number) // 2. If a component is unmounted during a parent component's update, // its update can be skipped. // Jobs can never be null before flush starts, since they are only invalidated // during execution of another flushed job. queue.sort((a, b) => getId(a!) - getId(b!)) while ((job = queue.shift()) !== undefined) { if (job === null) { continue } if (__DEV__) { checkRecursiveUpdates(seen!, job) } callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } flushPostFlushCbs(seen) isFlushing = false // some postFlushCb queued jobs! // keep flushing until it drains. if (queue.length || postFlushCbs.length) { flushJobs(seen) } }
好了,實現全在上面了,好像尚未解開咱們的疑問,咱們須要搞清楚 queueJob
及 queuePostFlushCb
是怎麼被調用的
// renderer.ts function createDevEffectOptions( instance: ComponentInternalInstance ): ReactiveEffectOptions { return { scheduler: queueJob, onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0, onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0 } } // effect.ts const run = (effect: ReactiveEffect) => { ... if (effect.options.scheduler) { effect.options.scheduler(effect) } else { effect() } }
看到這裏有沒有恍然大悟的感受?原來當響應式對象發生改變後,執行 effect
若是有 scheduler
這個參數,會執行這個 scheduler
函數,而且把 effect
當作參數傳入
繞口了,簡單點就是 queueJob(effect)
,嗯,清楚了,這也是數據發生改變後頁面不會當即更新的緣由
爲何要用nextTick
一個例子讓你們明白
{{num}} for(let i=0; i<100000; i++){ num = i }
若是沒有 nextTick
更新機制,那麼 num
每次更新值都會觸發視圖更新,有了nextTick
機制,只須要更新一次,因此爲何有nextTick
存在,相信你們內心已經有答案了。
總結
nextTick
是 vue
中的更新策略,也是性能優化手段,基於JS
執行機制實現
vue
中咱們改變數據時不會當即觸發視圖,若是須要實時獲取到最新的DOM
,這個時候能夠手動調用 nextTick
本文由博客一文多發平臺 OpenWrite 發佈!