全面解析Vue中的$nextTick
當(dāng)在代碼中更新了數(shù)據(jù),并希望等到對應(yīng)的Dom更新之后,再執(zhí)行一些邏輯。這時(shí),我們就會用到$nextTick
funcion callback(){ //等待Dom更新,然后搞點(diǎn)事。 } $nextTick(callback);
官方文檔對nextTick的解釋是:
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。
那么,Vue是如何做的這一點(diǎn)的,是不是在調(diào)用修改Dom的Api之后(appendChild, textContent = "xxxxx" 諸如此類),調(diào)用了我們的回調(diào)函數(shù)?
實(shí)際上發(fā)生了什么呢。
源碼
nextTick的實(shí)現(xiàn)邏輯在這個(gè)文件里:
vue/src/core/util/next-tick.js
我們調(diào)用的this.$nextTick實(shí)際上是這個(gè)方法:
export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
可以看到
- 回調(diào)函數(shù)被存放到了一個(gè)數(shù)組里:callbacks。
- 如果沒有傳遞回調(diào)函數(shù),這個(gè)方法會返回一個(gè)Promise,然后吧reslove當(dāng)成回調(diào)函數(shù)放到flushCallbacks中。所以文檔解釋了把本該當(dāng)成回調(diào)函數(shù)的callbacks放到then里的用法。
- 然后,有一個(gè)變量叫pending,如果不在pending中,則執(zhí)行函數(shù)timerFunc。而且pending默認(rèn)等于false。
- flushCallbacks這個(gè)函數(shù)會一口氣執(zhí)行所有回調(diào)函數(shù)。
timerFunc
timerFunc定義在這里
可以看到timerFunc是在一個(gè)已resolve了的Promise的then 中執(zhí)行了flushCallbacks.
利用了js事件循環(huán)的微任務(wù)的機(jī)制
所以,每當(dāng)我們調(diào)用$nextTick,如果pending為false,就會調(diào)用timerFunc,然后timerFunc會把flushCallbacks給塞到事件循環(huán)的隊(duì)尾,等待被調(diào)用。
if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) } }
flushCallbacks
然后在這個(gè)文件里還有一個(gè)函數(shù)叫:flushCallbacks
用來把保存的回調(diào)函數(shù)給全執(zhí)行并清空。
function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
pending
什么時(shí)候pending為true呢?
從timerFunc被調(diào)用到flushCallbacks被調(diào)用期間pending為true
即一個(gè)事件循環(huán)周期
在pending期間加入的回調(diào)函數(shù),會被已經(jīng)等待執(zhí)行的flushCallbacks函數(shù)給執(zhí)行。
核心機(jī)制
看完源碼,發(fā)現(xiàn)除了利用了一個(gè)微任務(wù)的機(jī)制,和Dom更新一點(diǎn)關(guān)系都沒有哇。
其實(shí)調(diào)用nextTick的不僅是開發(fā)者,Vue更新Dom時(shí),也用到了nextTick。
開發(fā)者更新綁定的數(shù)據(jù)之后,Vue就會立刻調(diào)用nextTick,把更新Dom的回調(diào)函數(shù)作為微任務(wù)塞到事件循環(huán)里去。
于是,在微任務(wù)隊(duì)列中,開發(fā)者調(diào)用的nextTick的回調(diào)函數(shù),就一定在更行Dom的回調(diào)函數(shù)之后執(zhí)行了。
但是問題又來了,根據(jù)瀏覽器的渲染機(jī)制,渲染線程是在微任務(wù)執(zhí)行完成之后運(yùn)行的。渲染線程沒運(yùn)行,怎么拿到Dom呢?
因?yàn)?,渲染線程只是把Dom樹渲染成UI而已,Vue更新Dom之后,在Dom樹里,新的Dom節(jié)點(diǎn)已經(jīng)存在了,js線程就已經(jīng)可以拿到新的Dom了。除非開發(fā)者讀取Dom的計(jì)算屬性,觸發(fā)了強(qiáng)制重流渲染線程才會打斷js線程。
總結(jié)
- 首先timerFunc函數(shù)負(fù)責(zé)把回調(diào)函數(shù)們都丟到事件循環(huán)的隊(duì)尾
- 然后,nextTick函數(shù)負(fù)責(zé)把回調(diào)函數(shù)們都保存起來。
- 調(diào)用nextTick函數(shù)時(shí)會調(diào)用timerFunc函數(shù)
- Vue更新Dom也會使用nextTick,而且在開發(fā)者調(diào)用nextTick之前。
- 因?yàn)?中的先后關(guān)系和事件循環(huán)的隊(duì)列性質(zhì),確保了開發(fā)者的nextTick的回調(diào)一定在Dom更新之后
以上就是解析Vue中的$nextTick的詳細(xì)內(nèi)容,更多關(guān)于Vue中的$nextTick的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue.js組件vue-waterfall-easy實(shí)現(xiàn)瀑布流效果
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)瀑布流之vue-waterfall-easy的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08如何刪除vue項(xiàng)目下的node_modules文件夾
這篇文章主要介紹了如何刪除vue項(xiàng)目下的node_modules文件夾,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題
這篇文章主要介紹了Vue使用babel-polyfill兼容IE解決白屏及語法報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue設(shè)計(jì)一個(gè)倒計(jì)時(shí)秒殺的組件詳解
這篇文章主要介紹了vue設(shè)計(jì)一個(gè)倒計(jì)時(shí)秒殺的組件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue+element?分頁封裝的實(shí)現(xiàn)示例
本文主要介紹了vue+element?分頁封裝的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07