<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的異步更新實(shí)現(xiàn)原理

          共 3511字,需瀏覽 8分鐘

           ·

          2021-01-19 09:10

          關(guān)注公眾號(hào) 程序員成長(zhǎng)指北,回復(fù)“加群

          加入我們一起學(xué)習(xí),天天進(jìn)步

          作者:Liqiuyue

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

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

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

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

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

          這就涉及到Vue底層的異步更新原理,也要說(shuō)一說(shuō)nextTick的實(shí)現(xiàn)。不過(guò)在說(shuō)nextTick之前,有必要先介紹一下JS的事件運(yùn)行機(jī)制。

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

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

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

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

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

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

          nextTick原理

          派發(fā)更新

          大家都知道vue的響應(yīng)式的靠依賴收集和派發(fā)更新來(lái)實(shí)現(xiàn)的。在修改數(shù)組之后的派發(fā)更新過(guò)程,會(huì)觸發(fā)setter的邏輯,執(zhí)行dep.notify()

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

          遍歷subs里每一個(gè)Watcher實(shí)例,然后調(diào)用實(shí)例的update方法,下面我們來(lái)看看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
          ????//根據(jù)id是否重復(fù)做優(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函數(shù):?Flush?both?queues?and?run?the?watchers
          ?????????nextTick(flushSchedulerQueue)
          ?????}
          ????}
          }

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

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

          這里也解釋了為什么for循環(huán)不能導(dǎo)致頁(yè)面更新,因?yàn)?code style="">for是主線程的代碼,在一開始執(zhí)行數(shù)據(jù)改變就會(huì)將它push到queue里,等到for里的代碼執(zhí)行完畢后i的值已經(jīng)變化為100時(shí),這時(shí)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()
          ??}

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

          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是在判斷不同的設(shè)備和不同情況下選用哪種特性去實(shí)現(xiàn)異步任務(wù):優(yōu)先檢測(cè)是否原生?持Promise,不?持的話再去檢測(cè)是否?持MutationObserver,如果都不行就只能嘗試宏任務(wù)實(shí)現(xiàn),首先是setImmediate,這是?個(gè)?版本 IE 和 Edge 才?持的特性,如果都不?持的話最后就會(huì)降級(jí)為 setTimeout 0。

          這?使?callbacks?不是直接在nextTick中執(zhí)?回調(diào)函數(shù)的原因是保證在同?個(gè) tick 內(nèi)多次執(zhí)?nextTick,不會(huì)開啟多個(gè)異步任務(wù),?把這些異步任務(wù)都?jí)撼?個(gè)同步任務(wù),在下?個(gè) tick 執(zhí)?完畢。

          nextTick使用

          nextTick不僅是vue的源碼文件,更是vue的一個(gè)全局API。下面來(lái)看看怎么使用吧。

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

          官網(wǎng)用例:

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

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

          并且因?yàn)?code style="">$nextTick() 返回一個(gè) Promise 對(duì)象,所以也可以使用async/await 語(yǔ)法去處理事件,非常方便。

          相關(guān)文章

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

          最后

          ??愛心三連擊

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

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。




          分享和在看就是最大的支持??
          瀏覽 75
          點(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群 | 国产日韩在线欧美视频免费观看 | 亚洲大乱婬交换 | 日韩一级片在现观看视频 | 色鬼色综合|