1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Vue的異步更新實現原理

        共 3511字,需瀏覽 8分鐘

         ·

        2021-01-19 09:10

        關注公眾號 程序員成長指北,回復“加群

        加入我們一起學習,天天進步

        作者:Liqiuyue

        鏈接:https://juejin.cn/post/6908264284032073736

        最近面試總是會被問到這么一個問題:在使用vue的時候,將for循環(huán)中聲明的變量i從1增加到100,然后將i展示到頁面上,頁面上的i是從1跳到100,還是會怎樣?答案當然是只會顯示100,并不會有跳轉的過程。

        怎么可以讓頁面上有從1到100顯示的過程呢,就是用setTimeout或者Promise.then等方法去模擬。

        講道理,如果不在vue里,單獨運行這段程序的話,輸出一定是從1到100,但是為什么在vue中就不一樣了呢?

        for(let?i=1;?i<=100;?i++){
        ?console.log(i);
        }

        這就涉及到Vue底層的異步更新原理,也要說一說nextTick的實現。不過在說nextTick之前,有必要先介紹一下JS的事件運行機制。

        JS運行機制

        眾所周知,JS是基于事件循環(huán)的單線程的語言。執(zhí)行的步驟大致是:

        1. 當代碼執(zhí)行時,所有同步的任務都在主線程上執(zhí)行,形成一個執(zhí)行棧;
        2. 在主線程之外還有一個任務隊列(task queue),只要異步任務有了運行結果就在任務隊列中放置一個事件;
        3. 一旦執(zhí)行棧中所有同步任務執(zhí)行完畢(主線程代碼執(zhí)行完畢),此時主線程不會空閑而是去讀取任務隊列。此時,異步的任務就結束等待的狀態(tài)被執(zhí)行。
        4. 主線程不斷重復以上的步驟。

        我們把主線程執(zhí)行一次的過程叫一個tick,所以nextTick就是下一個tick的意思,也就是說用nextTick的場景就是我們想在下一個tick做一些事的時候。

        所有的異步任務結果都是通過任務隊列來調度的。而任務分為兩類:宏任務(macro task)和微任務(micro task)。它們之間的執(zhí)行規(guī)則就是每個宏任務結束后都要將所有微任務清空。常見的宏任務有setTimeout/MessageChannel/postMessage/setImmediate,微任務有MutationObsever/Promise.then。

        想要透徹學習事件循環(huán),推薦Jake在JavaScript全球開發(fā)者大會的演講,保證講懂!

        nextTick原理

        派發(fā)更新

        大家都知道vue的響應式的靠依賴收集和派發(fā)更新來實現的。在修改數組之后的派發(fā)更新過程,會觸發(fā)setter的邏輯,執(zhí)行dep.notify()

        //?src/core/observer/watcher.js
        class?Dep?{
        ?notify()?{
        ?????//subs是Watcher的實例數組
        ?????const?subs?=?this.subs.slice()
        ????????for(let?i=0,?l=subs.length;?i?????????subs[i].update()
        ????????}
        ????}
        }

        遍歷subs里每一個Watcher實例,然后調用實例的update方法,下面我們來看看update是怎么去更新的:

        class?Watcher?{
        ?update()?{
        ?????...
        ?????//各種情況判斷之后
        ????????else{
        ?????????queueWatcher(this)
        ????????}
        ????}
        }

        update執(zhí)行后又走到了queueWatcher,那就繼續(xù)去看看queueWatcher干啥了(希望不要繼續(xù)套娃了:

        //queueWatcher?定義在?src/core/observer/scheduler.js
        const?queue:?Array?=?[]
        let?has:?{?[key:?number]:??true?}?=?{}
        let?waiting?=?false
        let?flushing?=?false
        let?index?=?0

        export?function?queueWatcher(watcher:?Watcher)?{
        ?const?id?=?watcher.id
        ????//根據id是否重復做優(yōu)化
        ????if(has[id]?==?null){
        ?????has[id]?=?true
        ????????if(!flushing){
        ?????????queue.push(watcher)
        ????????}else{
        ?????????let?i=queue.length?-?1
        ????????????while(i?>?index?&&?queue[i].id?>?watcher.id){
        ?????????????i--
        ????????????}
        ????????????queue.splice(i?+?1,?0,?watcher)
        ????????}
        ???????
        ?????if(!waiting){
        ??????waiting?=?true
        ?????????//flushSchedulerQueue函數:?Flush?both?queues?and?run?the?watchers
        ?????????nextTick(flushSchedulerQueue)
        ?????}
        ????}
        }

        這里queue在pushwatcher時是根據idflushing做了一些優(yōu)化的,并不會每次數據改變都觸發(fā)watcher的回調,而是把這些watcher先添加到?個隊列?,然后在nextTick后執(zhí)?flushSchedulerQueue。

        flushSchedulerQueue函數是保存更新事件的queue的一些加工,讓更新可以滿足Vue更新的生命周期。

        這里也解釋了為什么for循環(huán)不能導致頁面更新,因為for是主線程的代碼,在一開始執(zhí)行數據改變就會將它push到queue里,等到for里的代碼執(zhí)行完畢后i的值已經變化為100時,這時vue才走到nextTick(flushSchedulerQueue)這一步。

        nextTick源碼

        接著打開vue2.x的源碼,目錄core/util/next-tick.js,代碼量很小,加上注釋才110行,是比較好理解的。

        const?callbacks?=?[]
        let?pending?=?false

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

        首先將傳入的回調函數cb(上節(jié)的flushSchedulerQueue)壓入callbacks數組,最后通過timerFunc函數一次性解決。

        let?timerFunc

        if?(typeof?Promise?!==?'undefined'?&&?isNative(Promise))?{
        ??const?p?=?Promise.resolve()
        ??timerFunc?=?()?=>?{
        ????p.then(flushCallbacks)
        ????if?(isIOS)?setTimeout(noop)
        ????}
        ??isUsingMicroTask?=?true
        }?else?if?(!isIE?&&?typeof?MutationObserver?!==?'undefined'?&&?(
        ??isNative(MutationObserver)?||
        ??//?PhantomJS?and?iOS?7.x
        ??MutationObserver.toString()?===?'[object?MutationObserverConstructor]'
        ))?{
        ??let?counter?=?1
        ??const?observer?=?new?MutationObserver(flushCallbacks)
        ??const?textNode?=?document.createTextNode(String(counter))
        ??observer.observe(textNode,?{
        ????characterData:?true
        ??})
        ??timerFunc?=?()?=>?{
        ????counter?=?(counter?+?1)?%?2
        ????textNode.data?=?String(counter)
        ??}
        ??isUsingMicroTask?=?true
        }?else?if?(typeof?setImmediate?!==?'undefined'?&&?isNative(setImmediate))?{
        ??timerFunc?=?()?=>?{
        ????setImmediate(flushCallbacks)
        ??}
        }?else?{
        ??timerFunc?=?()?=>?{
        ????setTimeout(flushCallbacks,?0)
        ??}
        }

        timerFunc下面一大片if else是在判斷不同的設備和不同情況下選用哪種特性去實現異步任務:優(yōu)先檢測是否原生?持Promise,不?持的話再去檢測是否?持MutationObserver,如果都不行就只能嘗試宏任務實現,首先是setImmediate,這是?個?版本 IE 和 Edge 才?持的特性,如果都不?持的話最后就會降級為 setTimeout 0。

        這?使?callbacks?不是直接在nextTick中執(zhí)?回調函數的原因是保證在同?個 tick 內多次執(zhí)?nextTick,不會開啟多個異步任務,?把這些異步任務都壓成?個同步任務,在下?個 tick 執(zhí)?完畢。

        nextTick使用

        nextTick不僅是vue的源碼文件,更是vue的一個全局API。下面來看看怎么使用吧。

        當設置 vm.someData = 'new value',該組件不會立即重新渲染。當刷新隊列時,組件會在下一個事件循環(huán)tick中更新。多數情況我們不需要關心這個過程,但是如果你想基于更新后的 DOM 狀態(tài)來做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發(fā)人員使用數據驅動的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。為了在數據變化之后等待 Vue 完成更新 DOM,可以在數據變化之后立即使用Vue.nextTick(callback)。這樣回調函數將在 DOM 更新完成后被調用。

        官網用例:

        <div?id="example">{{message}}div>
        var?vm?=?new?Vue({
        ??el:?'#example',
        ??data:?{
        ????message:?'123'
        ??}
        })
        vm.message?=?'new?message'?//?更改數據

        vm.$el.textContent?===?'new?message'?//?false
        Vue.nextTick(function?()?{
        ??vm.$el.textContent?===?'new?message'?//?true
        })

        并且因為$nextTick() 返回一個 Promise 對象,所以也可以使用async/await 語法去處理事件,非常方便。

        相關文章

        1. 分享8個非常實用的Vue自定義指令
        2. Vue這些修飾符幫我節(jié)省20%的開發(fā)時間
        3. Vue路由權限控制分析

        最后

        ??愛心三連擊

        1.看到這里了就點個在看支持下吧,你的點贊在看是我創(chuàng)作的動力。

        2.關注公眾號程序員成長指北,回復「1」加入高級前端交流群!「在這里有好多 前端?開發(fā)者,會討論?前端 Node 知識,互相學習」!

        3.也可添加微信【ikoala520】,一起成長。




        分享和在看就是最大的支持??
        瀏覽 75
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中文字幕视频一区二区 | 精品人妻无码一区二区三区不卡 | 久久伊人视| 啊…高潮了 | 日逼视频免费网站 | 日韩一级毛| 国产1234在线观看 | 久久久久人妻一区精品色火影忍者 | 超碰伊人 | 欧美色拍|