<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

          共 6810字,需瀏覽 14分鐘

           ·

          2021-03-27 17:18

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

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

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

          我們從上面三個(gè)疑問大膽猜想一下

          • vue 更新DOM是有策略的,不是同步更新
          • nextTick 可以接收一個(gè)函數(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 是單線程語(yǔ)言,即指某一時(shí)間內(nèi)只能干一件事,有的同學(xué)可能會(huì)問,為什么 JS 不能是多線程呢?多線程就能同一時(shí)間內(nèi)干多件事情了

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

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

          概念

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

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

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

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

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

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

          image.png

          nextTick

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

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

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

          親自試一試

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

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

          queueJob and queuePostFlushCb

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

          處理列隊(duì),先對(duì)列隊(duì)進(jìn)行排序,執(zhí)行queue中的job,處理完后再處理postFlushCbs, 如果隊(duì)列沒有被清空會(huì)遞歸調(diào)用flushJobs清空隊(duì)列

          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)式對(duì)象發(fā)生改變后,執(zhí)行 effect 如果有 scheduler 這個(gè)參數(shù),會(huì)執(zhí)行這個(gè) scheduler 函數(shù),并且把 effect 當(dāng)做參數(shù)傳入

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

          effect傳送門[1]

          為什么要用nextTick

          一個(gè)例子讓大家明白

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

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

          總結(jié)

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

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

          參考資料

          [1]

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


          近期

          如果你喜歡探討技術(shù),或者對(duì)本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當(dāng)然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。魚頭的微信號(hào)是:krisChans95 也可以掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。公眾號(hào)窗口回復(fù)『 前端資料 』,即可獲取約 200M 前端面試資料,不要錯(cuò)過。


          若此文有用,何不素質(zhì)三連??
          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  国产真实露脸乱子伦对白高清视频 | 日日爱视频 | 那个视频可以看A片 | 小骚逼无码内射 | 亚洲成人大香蕉视频 |