您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue中的Vue.nextTick的異步怎么實(shí)現(xiàn)”,在日常操作中,相信很多人在Vue中的Vue.nextTick的異步怎么實(shí)現(xiàn)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue中的Vue.nextTick的異步怎么實(shí)現(xiàn)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
你真的了解nextTick嗎?來(lái),直接上題~
<template> <div id="app"> <p ref="name">{{ name }}</p> <button @click="handleClick">修改name</button> </div> </template> <script> export default { name: 'App', data () { return { name: '井柏然' } }, mounted() { console.log('mounted', this.$refs.name.innerText) }, methods: { handleClick () { this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText)) } } } </script>
請(qǐng)問(wèn)上述代碼中,當(dāng)點(diǎn)擊按鈕“修改name”時(shí),'nextTick1'
,'sync log'
,'nextTick2'
對(duì)應(yīng)的this.$refs.name.innerText
分別會(huì)輸出什么?注意,這里打印的是DOM的innerText~(文章結(jié)尾處會(huì)貼出答案)
如果此時(shí)的你有非常堅(jiān)定的答案,那你可以不用繼續(xù)往下看了~但如果你對(duì)自己的答案有所顧慮,那不如跟著我,接著往下看。相信你看完,不需要看到答案都能有個(gè)肯定的答案了~!
源碼位于core/util/next-tick中??梢詫⑵浞譃?個(gè)部分來(lái)看,直接上代碼
callbacks
隊(duì)列、pending
狀態(tài)
const callbacks = [] // 存放cb的隊(duì)列 let pending = false // 是否馬上遍歷隊(duì)列,執(zhí)行cb的標(biāo)志
flushCallbacks
遍歷callbacks執(zhí)行每個(gè)cb
function flushCallbacks () { pending = false // 注意這里,一旦執(zhí)行,pending馬上被重置為false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() // 執(zhí)行每個(gè)cb } }
nextTick
的異步實(shí)現(xiàn)根據(jù)執(zhí)行環(huán)境的支持程度采用不同的異步實(shí)現(xiàn)策略
let timerFunc // nextTick異步實(shí)現(xiàn)fn if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise方案 const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 將flushCallbacks包裝進(jìn)Promise.then中 } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // MutationObserver方案 let counter = 1 const observer = new MutationObserver(flushCallbacks) // 將flushCallbacks作為觀測(cè)變化的cb const textNode = document.createTextNode(String(counter)) // 創(chuàng)建文本節(jié)點(diǎn) // 觀測(cè)文本節(jié)點(diǎn)變化 observer.observe(textNode, { characterData: true }) // timerFunc改變文本節(jié)點(diǎn)的data,以觸發(fā)觀測(cè)的回調(diào)flushCallbacks timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate方案 timerFunc = () => { setImmediate(flushCallbacks) } } else { // 最終降級(jí)方案setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } }
這里用個(gè)真實(shí)案例加深對(duì)MutationObserver
的理解。畢竟比起其他三種異步方案,這個(gè)應(yīng)該是大家最陌生的
const observer = new MutationObserver(() => console.log('觀測(cè)到文本節(jié)點(diǎn)變化')) const textNode = document.createTextNode(String(1)) observer.observe(textNode, { characterData: true }) console.log('script start') setTimeout(() => console.log('timeout1')) textNode.data = String(2) // 這里對(duì)文本節(jié)點(diǎn)進(jìn)行值的修改 console.log('script end')
知道對(duì)應(yīng)的輸出會(huì)是怎么樣的嗎?
script start
、script end
會(huì)在第一輪宏任務(wù)中執(zhí)行,這點(diǎn)沒(méi)問(wèn)題
setTimeout
會(huì)被放入下一輪宏任務(wù)執(zhí)行
MutationObserver
是微任務(wù),所以會(huì)在本輪宏任務(wù)后執(zhí)行,所以先于setTimeout
結(jié)果如下圖:
nextTick
方法實(shí)現(xiàn)cb
、Promise
方式
export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 往全局的callbacks隊(duì)列中添加cb callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { // 這里是支持Promise的寫(xiě)法 _resolve(ctx) } }) if (!pending) { pending = true // 執(zhí)行timerFunc,在下一個(gè)Tick中執(zhí)行callbacks中的所有cb timerFunc() } // 對(duì)Promise的實(shí)現(xiàn),這也是我們使用時(shí)可以寫(xiě)成nextTick.then的原因 if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
深入細(xì)節(jié),理解pending
有什么用,如何運(yùn)作?
案例1,同一輪Tick中執(zhí)行2次$nextTick
,timerFunc
只會(huì)被執(zhí)行一次
this.$nextTick(() => console.log('nextTick1')) this.$nextTick(() => console.log('nextTick2'))
用圖看看更直觀?
這里如果有對(duì)Vue組件化、派發(fā)更新不是十分了解的朋友,可以先戳這里,看圖解Vue響應(yīng)式原理了解下Vue組件化和派發(fā)更新的相關(guān)內(nèi)容再回來(lái)看噢~
Vue的異步更新DOM其實(shí)也是使用nextTick
來(lái)實(shí)現(xiàn)的,跟我們平時(shí)使用的$nextTick其實(shí)是同一個(gè)~
這里我們回顧一下,當(dāng)我們改變一個(gè)屬性值的時(shí)候會(huì)發(fā)生什么?
根據(jù)上圖派發(fā)更新過(guò)程,我們從watcher.update開(kāi)時(shí)講起,以渲染W(wǎng)atcher為例,進(jìn)入到queueWatcher
里
queueWatcher
做了什么?// 用來(lái)存放Wathcer的隊(duì)列。注意,不要跟nextTick的callbacks搞混了,都是隊(duì)列,但用處不同~ const queue: Array<Watcher> = [] function queueWatcher (watcher: Watcher) { const id = watcher.id // 拿到Wathcer的id,這個(gè)id每個(gè)watcher都有且全局唯一 if (has[id] == null) { // 避免添加重復(fù)wathcer,這也是異步渲染的優(yōu)化做法 has[id] = true if (!flushing) { queue.push(watcher) } if (!waiting) { waiting = true // 這里把flushSchedulerQueue推進(jìn)nextTick的callbacks隊(duì)列中 nextTick(flushSchedulerQueue) } } }
flushSchedulerQueue
做了什么?function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // 排序保證先父后子執(zhí)行更新,保證userWatcher在渲染W(wǎng)atcher前 queue.sort((a, b) => a.id - b.id) // 遍歷所有的需要派發(fā)更新的Watcher執(zhí)行更新 for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null // 真正執(zhí)行派發(fā)更新,render -> update -> patch watcher.run() } }
最后,一張圖搞懂組件的異步更新過(guò)程
相信經(jīng)過(guò)上文對(duì)nextTick源碼的剖析,我們已經(jīng)揭開(kāi)它神秘的面紗了。這時(shí)的你一定可以堅(jiān)定地把答案說(shuō)出來(lái)了~話不多說(shuō),我們一起核實(shí)下,看看是不是如你所想!
1、如圖所示,mounted
時(shí)候的innerText是“井柏然”的中文
2、接下來(lái)是點(diǎn)擊按鈕后,打印結(jié)果如圖所示
沒(méi)錯(cuò),輸出結(jié)果如下(意不意外?驚不驚喜?)
sync log 井柏然
nextTick1 井柏然
nextTick2 jngboran
下面簡(jiǎn)單分析一下每個(gè)輸出:
this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
sync log
:這個(gè)同步打印沒(méi)什么好說(shuō)了,相信大部分童鞋的疑問(wèn)點(diǎn)都不在這里。如果不清楚的童鞋可以先回顧一下EventLoop,這里不多贅述了~
nextTick1
:注意其雖然是放在$nextTick
的回調(diào)中,在下一個(gè)tick執(zhí)行,但是他的位置是在this.name = 'jngboran'
的前。也就是說(shuō),他的cb會(huì)比App組件的派發(fā)更新(flushSchedulerQueue
)更先進(jìn)入隊(duì)列,當(dāng)nextTick1
打印時(shí),App組件還未派發(fā)更新,所以拿到的還是舊的DOM值。
nextTick2
就不展開(kāi)了,大家可以自行分析一下。相信大家對(duì)它應(yīng)該是最肯定的,我們平時(shí)不就是這樣拿到更新后的DOM嗎?
最后來(lái)一張圖加深理解
到此,關(guān)于“Vue中的Vue.nextTick的異步怎么實(shí)現(xiàn)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。