摘要:淺析的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),并不會(huì)立即更新。盡管已經(jīng)更新,但新增的元素并不立即插入到中。實(shí)際在中,執(zhí)行了,這也是自動(dòng)綁定到執(zhí)行上下文的原因。在內(nèi),使用數(shù)組保存回調(diào)函數(shù),表示當(dāng)前狀態(tài),使用函數(shù)來(lái)執(zhí)行回調(diào)隊(duì)列。
Vue.nextTick 淺析
Vue 的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),DOM 并不會(huì)立即更新。當(dāng)我們有一個(gè)業(yè)務(wù)場(chǎng)景,需要在 DOM 更新之后再執(zhí)行一段代碼時(shí),可以借助nextTick實(shí)現(xiàn)。以下是來(lái)自官方文檔的介紹:
將回調(diào)延遲到下次 DOM 更新循環(huán)之后執(zhí)行。在修改數(shù)據(jù)之后立即使用它,然后等待 DOM 更新。
具體的使用場(chǎng)景和底層代碼實(shí)現(xiàn)在后面的段落說(shuō)明和解釋。
用途Vue.nextTick([callback, context]) 與 vm.$nextTick([callback])
前者是全局方法,可以顯式指定執(zhí)行上下文,而后者是實(shí)例方法,執(zhí)行時(shí)自動(dòng)綁定this到當(dāng)前實(shí)例上。
此外,在 2.1.0 版本還新增了不傳入回調(diào)的使用方式,這種調(diào)用會(huì)返回一個(gè) Promise,在 then 的回調(diào)執(zhí)行目標(biāo)操作即可,如vm.$nextTick().then(cb)。
以下是一個(gè)nextTick使用例子:
{{count}}
- {{item}}
new Vue({ el: "#app", data: { count: 0, list: [] }, methods: { add() { this.count += 1; this.list.push(1); let li = this.$refs.ul.querySelectorAll("li"); li.forEach(item => { item.style.color = "red"; }); } } });
以上的代碼,期望在每次新增一個(gè)列表項(xiàng)時(shí)都使得列表項(xiàng)的字體是紅色的,但實(shí)際上新增的列表項(xiàng)字體仍是黑色的。盡管data已經(jīng)更新,但新增的 li 元素并不立即插入到 DOM 中。如果希望在 DOM 更新后再更新樣式,可以在nextTick的回調(diào)中執(zhí)行更新樣式的操作。
new Vue({ el: "#app", data: { count: 0, list: [] }, methods: { add() { this.count += 1; this.list.push(1); this.$nextTick(() => { let li = this.$refs.ul.querySelectorAll("li"); li.forEach(item => { item.style.color = "red"; }); }); } } });解釋
數(shù)據(jù)更新時(shí),并不會(huì)立即更新 DOM。如果在更新數(shù)據(jù)之后的代碼執(zhí)行另一段代碼,有可能達(dá)不到預(yù)想效果。將視圖更新后的操作放在nextTick的回調(diào)中執(zhí)行,其底層通過(guò)微任務(wù)的方式執(zhí)行回調(diào),可以保證 DOM 更新后才執(zhí)行代碼。
源碼在/src/core/instance/index.js,執(zhí)行方法renderMixin(Vue)為Vue.prototype添加了$nextTick方法。實(shí)際在Vue.prototype.$nextTick中,執(zhí)行了nextTick(fn, this),這也是vm.$nextTick( [callback] )自動(dòng)綁定this到執(zhí)行上下文的原因。
nextTick函數(shù)在/scr/core/util/next-tick.js聲明。在next-tick.js內(nèi),使用數(shù)組callbacks保存回調(diào)函數(shù),pending表示當(dāng)前狀態(tài),使用函數(shù)flushCallbacks來(lái)執(zhí)行回調(diào)隊(duì)列。在該方法內(nèi),先通過(guò)slice(0)保存了回調(diào)隊(duì)列的一個(gè)副本,通過(guò)設(shè)置callbacks.length = 0清空回調(diào)隊(duì)列,最后使用循環(huán)執(zhí)行在副本里的所有函數(shù)。
const callbacks = []; let pending = false; function flushCallbacks() { pending = false; const copies = callbacks.slice(0); callbacks.length = 0; for (let i = 0; i < copies.length; i++) { copies[i](); } }
接著定義函數(shù)marcoTimerFunc、microTimerFunc。
先判斷是否支持setImmediate,如果支持,使用setImmediate執(zhí)行回調(diào)隊(duì)列;如果不支持,判斷是否支持MessageChannel,支持時(shí),在port1監(jiān)聽(tīng)message,將flushCallbacks作為回調(diào);如果仍不支持MessageChannel,使用setTimeout(flushCallbacks, 0)執(zhí)行回調(diào)隊(duì)列。不管使用哪種方式,macroTimerFunc最終目的都是在一個(gè)宏任務(wù)里執(zhí)行回調(diào)隊(duì)列。
if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks); }; } else if ( typeof MessageChannel !== "undefined" && (isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === "[object MessageChannelConstructor]") ) { const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = flushCallbacks; macroTimerFunc = () => { port.postMessage(1); }; } else { /* istanbul ignore next */ macroTimerFunc = () => { setTimeout(flushCallbacks, 0); }; }
然后判斷是否支持Promise,支持時(shí),新建一個(gè)狀態(tài)為resolved的Promise對(duì)象,并在then回調(diào)里執(zhí)行回調(diào)隊(duì)列,如此,便在一個(gè)微任務(wù)中執(zhí)行回調(diào),在 IOS 的 UIWebViews 組件中,盡管能創(chuàng)建一個(gè)微任務(wù),但這個(gè)隊(duì)列并不會(huì)執(zhí)行,除非瀏覽器需要執(zhí)行其他任務(wù);所以使用setTimeout添加一個(gè)不執(zhí)行任何操作的回調(diào),使得微任務(wù)隊(duì)列被執(zhí)行。如果不支持Promise,使用降級(jí)方案,將microTimerFunc指向macroTimerFunc。
if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve(); microTimerFunc = () => { p.then(flushCallbacks); // in problematic UIWebViews, Promise.then doesn"t completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn"t being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop); }; } else { // fallback to macro microTimerFunc = macroTimerFunc; }
在函數(shù)nextTick內(nèi),先將函數(shù)cb使用箭頭函數(shù)包裝起來(lái)并添加到回調(diào)隊(duì)列callbacks,轉(zhuǎn)入的回調(diào)cb會(huì)在callbacks被遍歷執(zhí)行的時(shí)候執(zhí)行。如果沒(méi)有傳入cb,則是形如this.$nextTick().then(cb)的使用方式,所以要返回一個(gè)fulfilled的 Promise,在箭頭函數(shù)內(nèi)則需要執(zhí)行resolved,令返回的 Promise 狀態(tài)變?yōu)?b>fulfilled。接著判斷當(dāng)前是否正在執(zhí)行回調(diào),如果不是,將pengding設(shè)置為真。判斷回調(diào)執(zhí)行是宏任務(wù)還是微任務(wù),分別通過(guò)marcoTimerFunc、microTimerFunc來(lái)觸發(fā)回調(diào)隊(duì)列。最后,如果,沒(méi)有傳入cb,則需要?jiǎng)?chuàng)建一個(gè)Promise實(shí)例并返回以支持鏈?zhǔn)秸{(diào)用,并且將_resolve指向返回 Promise 的resolve函數(shù)。
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; if (useMacroTask) { macroTimerFunc(); } else { microTimerFunc(); } } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve; }); } }
而全局方法Vue.nextTick在/src/core/global-api/index.js中聲明,是對(duì)函數(shù)nextTick的引用,所以使用時(shí)可以顯式指定執(zhí)行上下文。
Vue.nextTick = nextTick;小結(jié)
本文關(guān)于nextTick的使用場(chǎng)景和源碼做了簡(jiǎn)單的介紹,如果想深入了解這部分的知識(shí),可以去了解一下微任務(wù)mircotask和宏任務(wù)marcotask。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/108549.html
摘要:核心的異步延遲函數(shù),用于異步延遲調(diào)用函數(shù)優(yōu)先使用原生原本支持更廣,但在的中,觸摸事件處理程序中觸發(fā)會(huì)產(chǎn)生嚴(yán)重錯(cuò)誤的,回調(diào)被推入隊(duì)列但是隊(duì)列可能不會(huì)如期執(zhí)行。 淺析 Vue 2.6 中的 nextTick 方法。 事件循環(huán) JS 的 事件循環(huán) 和 任務(wù)隊(duì)列 其實(shí)是理解 nextTick 概念的關(guān)鍵。這個(gè)網(wǎng)上其實(shí)有很多優(yōu)質(zhì)的文章做了詳細(xì)介紹,我就簡(jiǎn)單過(guò)過(guò)了。 以下內(nèi)容適用于瀏覽器端 JS,...
摘要:哪吒別人的看法都是狗屁,你是誰(shuí)只有你自己說(shuō)了才算,這是爹教我的道理。哪吒去他個(gè)鳥(niǎo)命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰(shuí)和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:因?yàn)槠綍r(shí)使用都是傳回調(diào)的,所以很好奇什么情況下會(huì)為,去翻看官方文檔發(fā)現(xiàn)起新增如果沒(méi)有提供回調(diào)且在支持的環(huán)境中,則返回一個(gè)。這就對(duì)了,函數(shù)體內(nèi)最后的判斷很明顯就是這個(gè)意思沒(méi)有回調(diào)支持。 Firstly, this paper is based on Vue 2.6.8剛開(kāi)始接觸Vue的時(shí)候,哇nextTick好強(qiáng),咋就在這里面寫(xiě)就是dom更新之后,當(dāng)時(shí)連什么macrotask、micro...
摘要:在查詢了各種資料后,總結(jié)了一下其原理和用途,如有錯(cuò)誤,請(qǐng)不吝賜教。截取關(guān)鍵部分如下具體來(lái)說(shuō),異步執(zhí)行的運(yùn)行機(jī)制如下。知乎上的例子改變數(shù)據(jù)想要立即使用更新后的。需要注意的是,在和階段,如果需要操作渲染后的試圖,也要使用方法。 對(duì)于 Vue.nextTick 方法,自己有些疑惑。在查詢了各種資料后,總結(jié)了一下其原理和用途,如有錯(cuò)誤,請(qǐng)不吝賜教。 概覽 官方文檔說(shuō)明: 用法: 在下次 DO...
閱讀 2628·2021-09-06 15:02
閱讀 3305·2021-09-02 10:18
閱讀 2903·2019-08-30 15:44
閱讀 758·2019-08-30 15:43
閱讀 2019·2019-08-30 14:08
閱讀 2818·2019-08-30 13:16
閱讀 1481·2019-08-26 13:52
閱讀 984·2019-08-26 12:21