<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          純干貨!聊聊你不知道的 Vue.nextTick

          共 5590字,需瀏覽 12分鐘

           ·

          2022-04-07 21:16

          點(diǎn)擊上方?前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群

          用過Vue的朋友多多少少都知道$nextTick~ 在正式講解nextTick之前,我想你應(yīng)該清楚知道 Vue 在更新 DOM 時(shí)是異步執(zhí)行的,因?yàn)榻酉聛碇v解過程會結(jié)合組件更新一起講~ 事不宜遲,我們直進(jìn)主題吧(本文以v2.6.14版本的Vue源碼進(jìn)行講解)

          一、nextTick小測試

          你真的了解nextTick嗎?來,直接上題~

          <template>
          ??<div?id="app">
          ????<p?ref="name">{{?name?}}p>
          ????<button?@click="handleClick">修改namebutton>
          ??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
          >
          復(fù)制代碼

          請問上述代碼中,當(dāng)點(diǎn)擊按鈕“修改name”時(shí),'nextTick1''sync log','nextTick2'對應(yīng)的this.$refs.name.innerText分別會輸出什么?注意,這里打印的是DOM的innerText~(文章結(jié)尾處會貼出答案)

          如果此時(shí)的你有非常堅(jiān)定的答案,那你可以不用繼續(xù)往下看了~但如果你對自己的答案有所顧慮,那不如跟著我,接著往下看。相信你看完,不需要看到答案都能有個肯定的答案了~!


          二、nextTick源碼實(shí)現(xiàn)

          源碼位于core/util/next-tick中??梢詫⑵浞譃?個部分來看,直接上代碼

          1. 全局變量

          callbacks隊(duì)列、pending狀態(tài)

          const?callbacks?=?[]?//?存放cb的隊(duì)列
          let?pending?=?false?//?是否馬上遍歷隊(duì)列,執(zhí)行cb的標(biāo)志
          復(fù)制代碼

          2. flushCallbacks

          遍歷callbacks執(zhí)行每個cb

          function?flushCallbacks?()?{
          ??pending?=?false?//?注意這里,一旦執(zhí)行,pending馬上被重置為false
          ??const?copies?=?callbacks.slice(0)
          ??callbacks.length?=?0
          ??for?(let?i?=?0;?i?????copies[i]()?//?執(zhí)行每個cb
          ??}
          }
          復(fù)制代碼

          3. 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作為觀測變化的cb
          ??const?textNode?=?document.createTextNode(String(counter))?//?創(chuàng)建文本節(jié)點(diǎn)
          ??//?觀測文本節(jié)點(diǎn)變化
          ??observer.observe(textNode,?{
          ????characterData:?true
          ??})
          ??//?timerFunc改變文本節(jié)點(diǎn)的data,以觸發(fā)觀測的回調(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?{
          ??//?最終降級方案setTimeout
          ??timerFunc?=?()?=>?{
          ????setTimeout(flushCallbacks,?0)
          ??}
          }
          復(fù)制代碼
          • 這里用個真實(shí)案例加深對MutationObserver的理解。畢竟比起其他三種異步方案,這個應(yīng)該是大家最陌生的
            const?observer?=?new?MutationObserver(()?=>?console.log('觀測到文本節(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)?//?這里對文本節(jié)點(diǎn)進(jìn)行值的修改
            console.log('script?end')
            復(fù)制代碼
          • 知道對應(yīng)的輸出會是怎么樣的嗎?
            1. script start、script end會在第一輪宏任務(wù)中執(zhí)行,這點(diǎn)沒問題
            2. setTimeout會被放入下一輪宏任務(wù)執(zhí)行
            3. MutationObserver是微任務(wù),所以會在本輪宏任務(wù)后執(zhí)行,所以先于setTimeout
          • 結(jié)果如下圖:

          4. 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的寫法
          ??????_resolve(ctx)
          ????}
          ??})
          ??if?(!pending)?{
          ????pending?=?true
          ????//?執(zhí)行timerFunc,在下一個Tick中執(zhí)行callbacks中的所有cb
          ????timerFunc()
          ??}
          ??//?對Promise的實(shí)現(xiàn),這也是我們使用時(shí)可以寫成nextTick.then的原因
          ??if?(!cb?&&?typeof?Promise?!==?'undefined')?{
          ????return?new?Promise(resolve?=>?{
          ??????_resolve?=?resolve
          ????})
          ??}
          }
          復(fù)制代碼
          • 深入細(xì)節(jié),理解pending有什么用,如何運(yùn)作?
          1. 案例1,同一輪Tick中執(zhí)行2次`$nextTick`,`timerFunc`只會被執(zhí)行一次
          this.$nextTick(()?=>?console.log('nextTick1'))
          this.$nextTick(()?=>?console.log('nextTick2'))
          復(fù)制代碼
          • 用圖看看更直觀?
          tick——pending.png

          三、Vue組件的異步更新

          這里如果有對Vue組件化、派發(fā)更新不是十分了解的朋友,可以先戳這里,看圖解Vue響應(yīng)式原理[1]了解下Vue組件化和派發(fā)更新的相關(guān)內(nèi)容再回來看噢~

          Vue的異步更新DOM其實(shí)也是使用nextTick來實(shí)現(xiàn)的,跟我們平時(shí)使用的$nextTick其實(shí)是同一個~

          這里我們回顧一下,當(dāng)我們改變一個屬性值的時(shí)候會發(fā)生什么?

          根據(jù)上圖派發(fā)更新過程,我們從watcher.update開時(shí)講起,以渲染W(wǎng)atcher為例,進(jìn)入到queueWatcher

          1. queueWatcher做了什么?

          //?用來存放Wathcer的隊(duì)列。注意,不要跟nextTick的callbacks搞混了,都是隊(duì)列,但用處不同~
          const?queue:?Array?=?[]

          function?queueWatcher?(watcher:?Watcher)?{
          ??const?id?=?watcher.id?//?拿到Wathcer的id,這個id每個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)
          ????}
          ??}
          }
          復(fù)制代碼

          2. 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?????watcher?=?queue[index]
          ????id?=?watcher.id
          ????has[id]?=?null
          ????//?真正執(zhí)行派發(fā)更新,render?->?update?->?patch
          ????watcher.run()
          ??}
          }
          復(fù)制代碼
          • 最后,一張圖搞懂組件的異步更新過程
          異步更新.png

          四、回歸題目本身

          相信經(jīng)過上文對nextTick源碼的剖析,我們已經(jīng)揭開它神秘的面紗了。這時(shí)的你一定可以堅(jiān)定地把答案說出來了~話不多說,我們一起核實(shí)下,看看是不是如你所想!

          1. 如圖所示,mounted時(shí)候的innerText是“井柏然”的中文

          2. 接下來是點(diǎn)擊按鈕后,打印結(jié)果如圖所示

          • 沒錯,輸出結(jié)果如下(意不意外?驚不驚喜?)

            1. sync log 井柏然
            2. nextTick1 井柏然
            3. nextTick2 jngboran
          • 下面簡單分析一下每個輸出:

            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))
            復(fù)制代碼
            1. sync log:這個同步打印沒什么好說了,相信大部分童鞋的疑問點(diǎn)都不在這里。如果不清楚的童鞋可以先回顧一下EventLoop,這里不多贅述了~
            2. nextTick1:注意其雖然是放在$nextTick的回調(diào)中,在下一個tick執(zhí)行,但是他的位置是在this.name = 'jngboran'的前。也就是說,他的cb會App組件的派發(fā)更新(flushSchedulerQueue)更先進(jìn)入隊(duì)列,當(dāng)nextTick1打印時(shí),App組件還未派發(fā)更新,所以拿到的還是舊的DOM值。
            3. nextTick2就不展開了,大家可以自行分析一下。相信大家對它應(yīng)該是最肯定的,我們平時(shí)不就是這樣拿到更新后的DOM嗎?
          • 最后來一張圖加深理解

          nextTick1.png

          寫在最后,nextTick其實(shí)在Vue中也算是比較核心的一個東西了。因?yàn)樨灤┱麄€Vue應(yīng)用的組件化、響應(yīng)式的派發(fā)更新與其息息相關(guān)~深入理解nextTick的背后實(shí)現(xiàn)原理,不僅能讓你在面試的時(shí)候一展風(fēng)采,更能讓你在日常開發(fā)工作中,少走彎路少踩坑!好了,本文到這里就暫告一段落了,如果讀完能讓你有所收獲,就幫忙點(diǎn)個贊吧~畫圖不易、創(chuàng)作艱辛鴨~

          關(guān)于本文

          作者:井柏然

          https://juejin.cn/post/7077181211029798942



          往期推薦


          秒啊!答好這5個問題,就入門Docker了
          (實(shí)戰(zhàn)篇)Vue + Node.js 從 0 到 1 實(shí)現(xiàn)自動化部署工具
          Vue3!煥然一新的 Vue3 中文文檔來了!

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...

          點(diǎn)個在看支持我吧
          瀏覽 29
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人另类重口味人兽在线观看 | 狠狠干综合| 久久久香蕉视频 | 人人妻人人爱人人操 | 久99久热 |