<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>

          Vue3源碼 nextTick解析

          共 4017字,需瀏覽 9分鐘

           ·

          2020-11-25 05:31

          定義: 在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM

          看完是不是有一堆問號?我們從中找出來產(chǎn)生問號的關(guān)鍵詞

          • 下次 DOM 更新循環(huán)結(jié)束之后?
          • 執(zhí)行延遲回調(diào)?
          • 更新后的 DOM?

          我們從上面三個疑問大膽猜想一下

          • vue 更新DOM是有策略的,不是同步更新
          • nextTick 可以接收一個函數(shù)做為入?yún)?/section>
          • nextTick 后能拿到最新的數(shù)據(jù)

          好了,我們問題都拋出來了,先來看一下如何使用

          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')
          ????}
          ??}
          })

          親自試一試

          那么 nextTick 是怎么做到的呢?為了后面的內(nèi)容更好理解,這里我們得從 js 的執(zhí)行機(jī)制說起

          JS執(zhí)行機(jī)制

          我們都知道 JS 是單線程語言,即指某一時間內(nèi)只能干一件事,有的同學(xué)可能會問,為什么 JS 不能是多線程呢?多線程就能同一時間內(nèi)干多件事情了

          是否多線程這個取決于語言的用途,一個很簡單的例子,如果同一時間,一個添加了 DOM,一個刪除了 DOM, 這個時候語言就不知道是該添還是該刪了,所以從應(yīng)用場景來看 JS 只能是單線程

          單線程就意味著我們所有的任務(wù)都需要排隊,后面的任務(wù)必須等待前面的任務(wù)完成才能執(zhí)行,如果前面的任務(wù)耗時很長,一些從用戶角度上不需要等待的任務(wù)就會一直等待,這個從體驗(yàn)角度上來講是不可接受的,所以JS中就出現(xiàn)了異步的概念

          概念

          • 同步 在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù)
          • 異步 不進(jìn)入主線程、而進(jìn)入"任務(wù)隊列"(task queue)的任務(wù),只有"任務(wù)隊列"通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進(jìn)入主線程執(zhí)行

          運(yùn)行機(jī)制

          • (1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。

          • (2)主線程之外,還存在一個"任務(wù)隊列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊列"之中放置一個事件。

          • (3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。

          • (4)主線程不斷重復(fù)上面的第三步

          image.png

          nextTick

          現(xiàn)在我們回來vue中的nextTick

          實(shí)現(xiàn)很簡單,完全是基于語言執(zhí)行機(jī)制實(shí)現(xiàn),直接創(chuàng)建一個異步任務(wù),那么nextTick自然就達(dá)到在同步任務(wù)后執(zhí)行的目的

          const?p?=?Promise.resolve()
          export?function?nextTick(fn?:?()?=>?void):?Promise<void>?{
          ??return?fn???p.then(fn)?:?p
          }

          親自試一試

          看到這里,有的同學(xué)可能又會問,前面我們猜想的 DOM 更新也是異步任務(wù),那他們的這個執(zhí)行順序如何保證呢?

          別急,在源碼中nextTick還有幾個兄弟函數(shù),我們接著往下看

          queueJob and queuePostFlushCb

          queueJob 維護(hù)job列隊,有去重邏輯,保證任務(wù)的唯一性,每次調(diào)用去執(zhí)行 queueFlushqueuePostFlushCb 維護(hù)cb列隊,被調(diào)用的時候去重,每次調(diào)用去執(zhí)行 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

          開啟異步任務(wù)(nextTick)處理 flushJobs

          function?queueFlush()?{
          ??//?避免重復(fù)調(diào)用flushJobs
          ??if?(!isFlushing?&&?!isFlushPending)?{
          ????isFlushPending?=?true
          ????nextTick(flushJobs)
          ??}
          }

          flushJobs

          處理列隊,先對列隊進(jìn)行排序,執(zhí)行queue中的job,處理完后再處理postFlushCbs, 如果隊列沒有被清空會遞歸調(diào)用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)
          ??}
          }

          好了,實(shí)現(xiàn)全在上面了,好像還沒有解開我們的疑問,我們需要搞清楚 queueJobqueuePostFlushCb 是怎么被調(diào)用的

          //??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()
          ??}
          }

          看到這里有沒有恍然大悟的感覺?原來當(dāng)響應(yīng)式對象發(fā)生改變后,執(zhí)行 effect 如果有 scheduler 這個參數(shù),會執(zhí)行這個 scheduler 函數(shù),并且把 effect 當(dāng)做參數(shù)傳入

          繞口了,簡單點(diǎn)就是 queueJob(effect),嗯,清楚了,這也是數(shù)據(jù)發(fā)生改變后頁面不會立即更新的原因

          effect傳送門[1]

          為什么要用nextTick

          一個例子讓大家明白

          {{num}}
          for(let?i=0;?i<100000;?i++){
          ?num?=?i
          }

          如果沒有 nextTick ?更新機(jī)制,那么 num 每次更新值都會觸發(fā)視圖更新,有了nextTick機(jī)制,只需要更新一次,所以為什么有nextTick存在,相信大家心里已經(jīng)有答案了。

          總結(jié)

          nextTickvue 中的更新策略,也是性能優(yōu)化手段,基于JS執(zhí)行機(jī)制實(shí)現(xiàn)

          vue 中我們改變數(shù)據(jù)時不會立即觸發(fā)視圖,如果需要實(shí)時獲取到最新的DOM,這個時候可以手動調(diào)用 nextTick

          參考資料

          [1]

          effect傳送門: https://vue3js.cn/reactivity/effect.html

          瀏覽 45
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  亚洲精品久久久久毛片A级绿茶 | 久久久精品淫秽色情 | 在线婷婷伦理五月天 | 亚洲天堂777 | 欧洲色图亚洲色图 |