偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Vue進(jìn)階面試必問(wèn),異步更新機(jī)制和nextTick原理

開(kāi)發(fā) 前端
最近的社區(qū)涌現(xiàn)了一大票vue源碼閱讀類的文章,在下借這個(gè)機(jī)會(huì)從大家的文章和討論中汲取了一些營(yíng)養(yǎng),同時(shí)對(duì)一些閱讀源碼時(shí)的想法進(jìn)行總結(jié)。

vue已是目前國(guó)內(nèi)前端web端三分天下之一,同時(shí)也作為本人主要技術(shù)棧之一,在日常使用中知其然也好奇著所以然,另外最近的社區(qū)涌現(xiàn)了一大票vue源碼閱讀類的文章,在下借這個(gè)機(jī)會(huì)從大家的文章和討論中汲取了一些營(yíng)養(yǎng),同時(shí)對(duì)一些閱讀源碼時(shí)的想法進(jìn)行總結(jié),出產(chǎn)一些文章,作為自己思考的輸出 。

目標(biāo)Vue版本:2.5.17-beta.0

vue源碼注釋:https://github.com/SHERlocked93/vue-analysis

聲明:文章中源碼的語(yǔ)法都使用 Flow,并且源碼根據(jù)需要都有刪節(jié)(為了不被迷糊 @_@),如果要看完整版的請(qǐng)進(jìn)入上面的github地址,本文是系列文章,文章地址見(jiàn)底部~

1. 異步更新

上一篇文章我們?cè)谝蕾囀占淼捻憫?yīng)式化方法 defineReactive 中的 setter 訪問(wèn)器中有派發(fā)更新 dep.notify() 方法,這個(gè)方法會(huì)挨個(gè)通知在 dep 的 subs 中收集的訂閱自己變動(dòng)的watchers執(zhí)行update。一起來(lái)看看 update 方法的實(shí)現(xiàn): 

  1. // src/core/observer/watcher.js  
  2. /* Subscriber接口,當(dāng)依賴發(fā)生改變的時(shí)候進(jìn)行回調(diào) */  
  3. update() { 
  4.    if (this.computed) {  
  5.     // 一個(gè)computed watcher有兩種模式:activated lazy(默認(rèn))  
  6.     // 只有當(dāng)它被至少一個(gè)訂閱者依賴時(shí)才置activated,這通常是另一個(gè)計(jì)算屬性或組件的render function  
  7.     if (this.dep.subs.length === 0) { // 如果沒(méi)人訂閱這個(gè)計(jì)算屬性的變化  
  8.       // lazy時(shí),我們希望它只在必要時(shí)執(zhí)行計(jì)算,所以我們只是簡(jiǎn)單地將觀察者標(biāo)記為dirty  
  9.       // 當(dāng)計(jì)算屬性被訪問(wèn)時(shí),實(shí)際的計(jì)算在this.evaluate()中執(zhí)行  
  10.       this.dirty = true  
  11.     } else {  
  12.       // activated模式下,我們希望主動(dòng)執(zhí)行計(jì)算,但只有當(dāng)值確實(shí)發(fā)生變化時(shí)才通知我們的訂閱者  
  13.       this.getAndInvoke(() => {  
  14.         this.dep.notify() // 通知渲染watcher重新渲染,通知依賴自己的所有watcher執(zhí)行update  
  15.       })  
  16.     }  
  17.   } elseif (this.sync) {    // 同步  
  18.     this.run()  
  19.   } else {  
  20.     queueWatcher(this) // 異步推送到調(diào)度者觀察者隊(duì)列中,下一個(gè)tick時(shí)調(diào)用  
  21.   }  

如果不是 computed watcher 也非 sync 會(huì)把調(diào)用update的當(dāng)前watcher推送到調(diào)度者隊(duì)列中,下一個(gè)tick時(shí)調(diào)用,看看 queueWatcher : 

  1. // src/core/observer/scheduler.js  
  2. /* 將一個(gè)觀察者對(duì)象push進(jìn)觀察者隊(duì)列,在隊(duì)列中已經(jīng)存在相同的id則  
  3.  * 該watcher將被跳過(guò),除非它是在隊(duì)列正被flush時(shí)推送  
  4.  */  
  5. exportfunction queueWatcher (watcher: Watcher) {  
  6.   const id = watcher.id  
  7.   if (has[id] == null) { // 檢驗(yàn)id是否存在,已經(jīng)存在則直接跳過(guò),不存在則標(biāo)記哈希表has,用于下次檢驗(yàn)  
  8.     has[id] = true  
  9.     queue.push(watcher) // 如果沒(méi)有正在flush,直接push到隊(duì)列中  
  10.     if (!waiting) { // 標(biāo)記是否已傳給nextTick  
  11.       waiting = true  
  12.       nextTick(flushSchedulerQueue)  
  13.     }  
  14.   }  
  15.  
  16. /* 重置調(diào)度者狀態(tài) */  
  17. function resetSchedulerState () {  
  18.   queue.length = 0  
  19.   has = {}  
  20.   waiting = false  

這里使用了一個(gè) has 的哈希map用來(lái)檢查是否當(dāng)前watcher的id是否存在,若已存在則跳過(guò),不存在則就push到 queue 隊(duì)列中并標(biāo)記哈希表has,用于下次檢驗(yàn),防止重復(fù)添加。這就是一個(gè)去重的過(guò)程,比每次查重都要去queue中找要文明,在渲染的時(shí)候就不會(huì)重復(fù) patch 相同watcher的變化,這樣就算同步修改了一百次視圖中用到的data,異步 patch 的時(shí)候也只會(huì)更新最后一次修改。

這里的 waiting 方法是用來(lái)標(biāo)記 flushSchedulerQueue 是否已經(jīng)傳遞給 nextTick 的標(biāo)記位,如果已經(jīng)傳遞則只push到隊(duì)列中不傳遞 flushSchedulerQueue 給 nextTick,等到 resetSchedulerState 重置調(diào)度者狀態(tài)的時(shí)候 waiting 會(huì)被置回 false 允許 flushSchedulerQueue 被傳遞給下一個(gè)tick的回調(diào),總之保證了 flushSchedulerQueue 回調(diào)在一個(gè)tick內(nèi)只允許被傳入一次。來(lái)看看被傳遞給 nextTick 的回調(diào) flushSchedulerQueue 做了什么: 

  1. // src/core/observer/scheduler.js  
  2. /* nextTick的回調(diào)函數(shù),在下一個(gè)tick時(shí)flush掉兩個(gè)隊(duì)列同時(shí)運(yùn)行watchers */  
  3. function flushSchedulerQueue () {  
  4.   flushing = true  
  5.   let watcher, id  
  6.   queue.sort((a, b) => a.id - b.id) // 排序  
  7.   for (index = 0; index < queue.length; index++) {  // 不要將length進(jìn)行緩存  
  8.     watcher = queue[index]  
  9.     if (watcher.before) { // 如果watcher有before則執(zhí)行  
  10.       watcher.before()  
  11.     }  
  12.     id = watcher.id  
  13.     has[id] = null// 將has的標(biāo)記刪除  
  14.     watcher.run() // 執(zhí)行watcher  
  15.     if (process.env.NODE_ENV !== 'production' && has[id] != null) { // 在dev環(huán)境下檢查是否進(jìn)入死循環(huán)  
  16.       circular[id] = (circular[id] || 0) + 1// 比如user watcher訂閱自己的情況  
  17.       if (circular[id] > MAX_UPDATE_COUNT) { // 持續(xù)執(zhí)行了一百次watch代表可能存在死循環(huán)  
  18.         warn()  // 進(jìn)入死循環(huán)的警告  
  19.         break  
  20.       }  
  21.     }  
  22.   } 
  23.   resetSchedulerState() // 重置調(diào)度者狀態(tài)  
  24.   callActivatedHooks() // 使子組件狀態(tài)都置成active同時(shí)調(diào)用activated鉤子  
  25.   callUpdatedHooks() // 調(diào)用updated鉤子  

在 nextTick 方法中執(zhí)行 flushSchedulerQueue 方法,這個(gè)方法挨個(gè)執(zhí)行 queue 中的watcher的 run 方法。我們看到在首先有個(gè) queue.sort() 方法把隊(duì)列中的watcher按id從小到大排了個(gè)序,這樣做可以保證:

  1.  組件更新的順序是從父組件到子組件的順序,因?yàn)楦附M件總是比子組件先創(chuàng)建。
  2.  一個(gè)組件的user watchers(偵聽(tīng)器watcher)比render watcher先運(yùn)行,因?yàn)閡ser watchers往往比render watcher更早創(chuàng)建
  3.  如果一個(gè)組件在父組件watcher運(yùn)行期間被銷毀,它的watcher執(zhí)行將被跳過(guò)

在挨個(gè)執(zhí)行隊(duì)列中的for循環(huán)中,index < queue.length 這里沒(méi)有將length進(jìn)行緩存,因?yàn)樵趫?zhí)行處理現(xiàn)有watcher對(duì)象期間,更多的watcher對(duì)象可能會(huì)被push進(jìn)queue。

那么數(shù)據(jù)的修改從model層反映到view的過(guò)程:數(shù)據(jù)更改 -> setter -> Dep -> Watcher -> nextTick -> patch -> 更新視圖

2. nextTick原理

2.1 宏任務(wù)/微任務(wù)

這里就來(lái)看看包含著每個(gè)watcher執(zhí)行的方法被作為回調(diào)傳入 nextTick 之后,nextTick 對(duì)這個(gè)方法做了什么。不過(guò)首先要了解一下瀏覽器中的 EventLoop、macro task、micro task幾個(gè)概念,不了解可以參考一下 JS與Node.js中的事件循環(huán) 這篇文章,這里就用一張圖來(lái)表明一下后兩者在主線程中的執(zhí)行關(guān)系:

宏任務(wù)微任務(wù)

解釋一下,當(dāng)主線程執(zhí)行完同步任務(wù)后:

  1.  引擎首先從macrotask queue中取出第一個(gè)任務(wù),執(zhí)行完畢后,將microtask queue中的所有任務(wù)取出,按順序全部執(zhí)行;
  2.  然后再?gòu)膍acrotask queue中取下一個(gè),執(zhí)行完畢后,再次將microtask queue中的全部取出;
  3.  循環(huán)往復(fù),直到兩個(gè)queue中的任務(wù)都取完。

瀏覽器環(huán)境中常見(jiàn)的異步任務(wù)種類,按照優(yōu)先級(jí):

  •  macro task :同步代碼、setImmediate、MessageChannel、setTimeout/setInterval
  •  micro task:Promise.then、MutationObserver

有的文章把 micro task 叫微任務(wù),macro task 叫宏任務(wù),因?yàn)檫@兩個(gè)單詞拼寫太像了 -。- ,所以后面的注釋多用中文表示~

先來(lái)看看源碼中對(duì) micro task 與 macro task 的實(shí)現(xiàn):macroTimerFunc、microTimerFunc 

  1. // src/core/util/next-tick.js  
  2. const callbacks = [] // 存放異步執(zhí)行的回調(diào)  
  3. let pending = false// 一個(gè)標(biāo)記位,如果已經(jīng)有timerFunc被推送到任務(wù)隊(duì)列中去則不需要重復(fù)推送  
  4. /* 挨個(gè)同步執(zhí)行callbacks中回調(diào) */  
  5. function flushCallbacks() {  
  6.   pending = false  
  7.   const copies = callbacks.slice(0)  
  8.   callbacks.length = 0  
  9.   for (let i = 0; i < copies.length; i++) {  
  10.     copies[i]()  
  11.   }  
  12.   
  13. let microTimerFunc // 微任務(wù)執(zhí)行方法  
  14. let macroTimerFunc // 宏任務(wù)執(zhí)行方法  
  15. let useMacroTask = false// 是否強(qiáng)制為宏任務(wù),默認(rèn)使用微任務(wù)  
  16. // 宏任務(wù)  
  17. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {  
  18.   macroTimerFunc = () => { 
  19.      setImmediate(flushCallbacks)  
  20.   }  
  21. } elseif (typeof MessageChannel !== 'undefined' && (  
  22.   isNative(MessageChannel) ||  
  23.   MessageChannel.toString() === '[object MessageChannelConstructor]'// PhantomJS  
  24. )) {  
  25.   const channel = new MessageChannel()  
  26.   const port = channel.port2  
  27.   channel.port1.onmessage = flushCallbacks  
  28.   macroTimerFunc = () => {  
  29.     port.postMessage(1)  
  30.   }  
  31. } else {  
  32.   macroTimerFunc = () => {  
  33.     setTimeout(flushCallbacks, 0)  
  34.   }  
  35.  
  36. // 微任務(wù)  
  37. if (typeofPromise !== 'undefined' && isNative(Promise)) {  
  38.   const p = Promise.resolve()  
  39.   microTimerFunc = () => {  
  40.     p.then(flushCallbacks)  
  41.   }  
  42. } else {  
  43.   microTimerFunc = macroTimerFunc // fallback to macro  

flushCallbacks 這個(gè)方法就是挨個(gè)同步的去執(zhí)行callbacks中的回調(diào)函數(shù)們,callbacks中的回調(diào)函數(shù)是在調(diào)用 nextTick 的時(shí)候添加進(jìn)去的;那么怎么去使用 micro task 與 macro task 去執(zhí)行 flushCallbacks 呢,這里他們的實(shí)現(xiàn) macroTimerFunc、microTimerFunc 使用瀏覽器中宏任務(wù)/微任務(wù)的API對(duì)flushCallbacks 方法進(jìn)行了一層包裝。比如宏任務(wù)方法 macroTimerFunc=()=>{ setImmediate(flushCallbacks) },這樣在觸發(fā)宏任務(wù)執(zhí)行的時(shí)候 macroTimerFunc() 就可以在瀏覽器中的下一個(gè)宏任務(wù)loop的時(shí)候消費(fèi)這些保存在callbacks數(shù)組中的回調(diào)了,微任務(wù)同理。同時(shí)也可以看出傳給 nextTick 的異步回調(diào)函數(shù)是被壓成了一個(gè)同步任務(wù)在一個(gè)tick執(zhí)行完的,而不是開(kāi)啟多個(gè)異步任務(wù)。

注意這里有個(gè)比較難理解的地方,第一次調(diào)用 nextTick 的時(shí)候 pending 為false,此時(shí)已經(jīng)push到瀏覽器event loop中一個(gè)宏任務(wù)或微任務(wù)的task,如果在沒(méi)有flush掉的情況下繼續(xù)往callbacks里面添加,那么在執(zhí)行這個(gè)占位queue的時(shí)候會(huì)執(zhí)行之后添加的回調(diào),所以 macroTimerFunc、microTimerFunc 相當(dāng)于task queue的占位,以后 pending 為true則繼續(xù)往占位queue里面添加,event loop輪到這個(gè)task queue的時(shí)候?qū)⒁徊?zhí)行。執(zhí)行 flushCallbacks 時(shí) pending 置false,允許下一輪執(zhí)行 nextTick 時(shí)往event loop占位。

可以看到上面 macroTimerFunc 與 microTimerFunc 進(jìn)行了在不同瀏覽器兼容性下的平穩(wěn)退化,或者說(shuō)降級(jí)策略:

    1.  macroTimerFunc :setImmediate -> MessageChannel -> setTimeout 。首先檢測(cè)是否原生支持 setImmediate ,這個(gè)方法只在 IE、Edge 瀏覽器中原生實(shí)現(xiàn),然后檢測(cè)是否支持 MessageChannel,如果對(duì) MessageChannel 不了解可以參考一下這篇文章,還不支持的話最后使用 setTimeout ;為什么優(yōu)先使用 setImmediate 與 MessageChannel 而不直接使用 setTimeout 呢,是因?yàn)镠TML5規(guī)定setTimeout執(zhí)行的最小延時(shí)為4ms,而嵌套的timeout表現(xiàn)為10ms,為了盡可能快的讓回調(diào)執(zhí)行,沒(méi)有最小延時(shí)限制的前兩者顯然要優(yōu)于 setTimeout。

    2.  microTimerFunc:Promise.then -> macroTimerFunc 。首先檢查是否支持 Promise,如果支持的話通過(guò) Promise.then 來(lái)調(diào)用 flushCallbacks 方法,否則退化為 macroTimerFunc ;vue2.5之后 nextTick 中因?yàn)榧嫒菪栽騽h除了微任務(wù)平穩(wěn)退化的 MutationObserver 的方式。

2.2 nextTick實(shí)現(xiàn)

最后來(lái)看看我們平常用到的 nextTick 方法到底是如何實(shí)現(xiàn)的: 

  1. // src/core/util/next-tick.js  
  2. exportfunction nextTick(cb?: Function, ctx?: Object) {  
  3.   let _resolve  
  4.   callbacks.push(() => {  
  5.     if (cb) {  
  6.       try {  
  7.         cb.call(ctx)  
  8.       } catch (e) {  
  9.         handleError(e, ctx, 'nextTick')  
  10.       }  
  11.     } elseif (_resolve) {  
  12.       _resolve(ctx)  
  13.     }  
  14.   })  
  15.   if (!pending) {  
  16.     pending = true  
  17.     if (useMacroTask) {  
  18.       macroTimerFunc()  
  19.     } else {  
  20.       microTimerFunc()  
  21.     }  
  22.   }  
  23.   if (!cb && typeofPromise !== 'undefined') {  
  24.     returnnewPromise(resolve => {  
  25.       _resolve = resolve  
  26.     })  
  27.   }  
  28.  
  29. /* 強(qiáng)制使用macrotask的方法 */  
  30. exportfunction withMacroTask(fn: Function): Function {  
  31.   return fn._withTask || (fn._withTask = function() {  
  32.     useMacroTask = true  
  33.     const res = fn.apply(null, arguments)  
  34.     useMacroTask = false  
  35.     return res  
  36.   })  

nextTick 在這里分為三個(gè)部分,我們一起來(lái)看一下;

    1.  首先 nextTick 把傳入的 cb 回調(diào)函數(shù)用 try-catch 包裹后放在一個(gè)匿名函數(shù)中推入callbacks數(shù)組中,這么做是因?yàn)榉乐箚蝹€(gè) cb 如果執(zhí)行錯(cuò)誤不至于讓整個(gè)JS線程掛掉,每個(gè) cb 都包裹是防止這些回調(diào)函數(shù)如果執(zhí)行錯(cuò)誤不會(huì)相互影響,比如前一個(gè)拋錯(cuò)了后一個(gè)仍然可以執(zhí)行。

    2.  然后檢查 pending 狀態(tài),這個(gè)跟之前介紹的 queueWatcher 中的 waiting 是一個(gè)意思,它是一個(gè)標(biāo)記位,一開(kāi)始是 false 在進(jìn)入 macroTimerFunc、microTimerFunc 方法前被置為 true,因此下次調(diào)用 nextTick 就不會(huì)進(jìn)入 macroTimerFunc、microTimerFunc 方法,這兩個(gè)方法中會(huì)在下一個(gè) macro/micro tick 時(shí)候 flushCallbacks 異步的去執(zhí)行callbacks隊(duì)列中收集的任務(wù),而 flushCallbacks 方法在執(zhí)行一開(kāi)始會(huì)把 pending 置 false,因此下一次調(diào)用 nextTick 時(shí)候又能開(kāi)啟新一輪的 macroTimerFunc、microTimerFunc,這樣就形成了vue中的 event loop。

    3.  最后檢查是否傳入了 cb,因?yàn)?nextTick 還支持Promise化的調(diào)用:nextTick().then(() => {}),所以如果沒(méi)有傳入 cb 就直接return了一個(gè)Promise實(shí)例,并且把resolve傳遞給_resolve,這樣后者執(zhí)行的時(shí)候就跳到我們調(diào)用的時(shí)候傳遞進(jìn) then 的方法中。

Vue源碼中 next-tick.js 文件還有一段重要的注釋,這里就翻譯一下:

在vue2.5之前的版本中,nextTick基本上基于 micro task 來(lái)實(shí)現(xiàn)的,但是在某些情況下 micro task 具有太高的優(yōu)先級(jí),并且可能在連續(xù)順序事件之間(例如#4521,#6690)或者甚至在同一事件的事件冒泡過(guò)程中之間觸發(fā)(#6566)。但是如果全部都改成 macro task,對(duì)一些有重繪和動(dòng)畫的場(chǎng)景也會(huì)有性能影響,如 issue #6813。vue2.5之后版本提供的解決辦法是默認(rèn)使用 micro task,但在需要時(shí)(例如在v-on附加的事件處理程序中)強(qiáng)制使用 macro task。

為什么默認(rèn)優(yōu)先使用 micro task 呢,是利用其高優(yōu)先級(jí)的特性,保證隊(duì)列中的微任務(wù)在一次循環(huán)全部執(zhí)行完畢。

強(qiáng)制 macro task 的方法是在綁定 DOM 事件的時(shí)候,默認(rèn)會(huì)給回調(diào)的 handler 函數(shù)調(diào)用 withMacroTask 方法做一層包裝 handler = withMacroTask(handler),它保證整個(gè)回調(diào)函數(shù)執(zhí)行過(guò)程中,遇到數(shù)據(jù)狀態(tài)的改變,這些改變都會(huì)被推到 macro task 中。以上實(shí)現(xiàn)在 src/platforms/web/runtime/modules/events.js 的 add 方法中,可以自己看一看具體代碼。

剛好在寫這篇文章的時(shí)候思否上有人問(wèn)了個(gè)問(wèn)題 vue 2.4 和2.5 版本的@input事件不一樣 ,這個(gè)問(wèn)題的原因也是因?yàn)?.5之前版本的DOM事件采用 micro task ,而之后采用 macro task,解決的途徑參考 < Vue.js 升級(jí)踩坑小記> 中介紹的幾個(gè)辦法,這里就提供一個(gè)在mounted鉤子中用 addEventListener 添加原生事件的方法來(lái)實(shí)現(xiàn),參見(jiàn) CodePen。

3. 一個(gè)例子

說(shuō)這么多,不如來(lái)個(gè)例子,執(zhí)行參見(jiàn) CodePen 

  1. <div id="app">  
  2.   <span id='name' ref='name'>{{ name }}</span>  
  3.   <button @click='change'>change name</button>  
  4.   <div id='content'></div>  
  5. </div>  
  6. <script>  
  7.   new Vue({  
  8.     el: '#app',  
  9.     data() {  
  10.       return {  
  11.         name: 'SHERlocked93'  
  12.       }  
  13.     },  
  14.     methods: {  
  15.       change() {  
  16.         const $name = this.$refs.name  
  17.         this.$nextTick(() => console.log('setter前:' + $name.innerHTML))  
  18.         this.name = ' name改嘍 '  
  19.         console.log('同步方式:' + this.$refs.name.innerHTML)  
  20.         setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML))  
  21.         this.$nextTick(() => console.log('setter后:' + $name.innerHTML))  
  22.         this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML))  
  23.       }  
  24.     }  
  25.   })  
  26. </script> 

執(zhí)行以下看看結(jié)果: 

  1. 同步方式:SHERlocked93  
  2. setter前:SHERlocked93  
  3. setter后:name改嘍  
  4. Promise方式:name改嘍  
  5. setTimeout方式:name改嘍 

為什么是這樣的結(jié)果呢,解釋一下:

    1.  同步方式: 當(dāng)把data中的name修改之后,此時(shí)會(huì)觸發(fā)name的 setter 中的 dep.notify 通知依賴本data的render watcher去 update,update 會(huì)把 flushSchedulerQueue 函數(shù)傳遞給 nextTick,render watcher在 flushSchedulerQueue 函數(shù)運(yùn)行時(shí) watcher.run 再走 diff -> patch 那一套重渲染 re-render 視圖,這個(gè)過(guò)程中會(huì)重新依賴收集,這個(gè)過(guò)程是異步的;所以當(dāng)我們直接修改了name之后打印,這時(shí)異步的改動(dòng)還沒(méi)有被 patch 到視圖上,所以獲取視圖上的DOM元素還是原來(lái)的內(nèi)容。

    2.  setter前: setter前為什么還打印原來(lái)的是原來(lái)內(nèi)容呢,是因?yàn)?nextTick 在被調(diào)用的時(shí)候把回調(diào)挨個(gè)push進(jìn)callbacks數(shù)組,之后執(zhí)行的時(shí)候也是 for 循環(huán)出來(lái)挨個(gè)執(zhí)行,所以是類似于隊(duì)列這樣一個(gè)概念,先入先出;在修改name之后,觸發(fā)把render watcher填入 schedulerQueue 隊(duì)列并把他的執(zhí)行函數(shù) flushSchedulerQueue 傳遞給 nextTick ,此時(shí)callbacks隊(duì)列中已經(jīng)有了 setter前函數(shù) 了,因?yàn)檫@個(gè) cb 是在 setter前函數(shù) 之后被push進(jìn)callbacks隊(duì)列的,那么先入先出的執(zhí)行callbacks中回調(diào)的時(shí)候先執(zhí)行 setter前函數(shù),這時(shí)并未執(zhí)行render watcher的 watcher.run,所以打印DOM元素仍然是原來(lái)的內(nèi)容。

    3.  setter后: setter后這時(shí)已經(jīng)執(zhí)行完 flushSchedulerQueue,這時(shí)render watcher已經(jīng)把改動(dòng) patch 到視圖上,所以此時(shí)獲取DOM是改過(guò)之后的內(nèi)容。

    4.  Promise方式: 相當(dāng)于 Promise.then 的方式執(zhí)行這個(gè)函數(shù),此時(shí)DOM已經(jīng)更改。

    5.  setTimeout方式: 最后執(zhí)行macro task的任務(wù),此時(shí)DOM已經(jīng)更改。

注意,在執(zhí)行 setter前函數(shù) 這個(gè)異步任務(wù)之前,同步的代碼已經(jīng)執(zhí)行完畢,異步的任務(wù)都還未執(zhí)行,所有的 $nextTick 函數(shù)也執(zhí)行完畢,所有回調(diào)都被push進(jìn)了callbacks隊(duì)列中等待執(zhí)行,所以在setter前函數(shù) 執(zhí)行的時(shí)候,此時(shí)callbacks隊(duì)列是這樣的:[setter前函數(shù),flushSchedulerQueue,setter后函數(shù),Promise方式函數(shù)],它是一個(gè)micro task隊(duì)列,執(zhí)行完畢之后執(zhí)行macro task setTimeout,所以打印出上面的結(jié)果。

另外,如果瀏覽器的宏任務(wù)隊(duì)列里面有setImmediate、MessageChannel、setTimeout/setInterval 各種類型的任務(wù),那么會(huì)按照上面的順序挨個(gè)按照添加進(jìn)event loop中的順序執(zhí)行,所以如果瀏覽器支持MessageChannel, nextTick 執(zhí)行的是 macroTimerFunc,那么如果 macrotask queue 中同時(shí)有 nextTick 添加的任務(wù)和用戶自己添加的 setTimeout 類型的任務(wù),會(huì)優(yōu)先執(zhí)行 nextTick 中的任務(wù),因?yàn)镸essageChannel 的優(yōu)先級(jí)比 setTimeout的高,setImmediate 同理。

責(zé)任編輯:龐桂玉 來(lái)源: 前端大全
相關(guān)推薦

2020-10-13 08:36:30

React 架構(gòu)機(jī)制

2021-12-08 06:53:28

Choreograph屏幕機(jī)制

2025-03-04 05:46:05

2011-07-15 09:57:03

MongoDB緩存刷新

2020-09-07 11:14:02

Vue異步更新

2020-04-26 08:21:43

javascriptVue

2010-09-06 08:43:13

.NET 4

2018-08-10 04:40:56

2023-02-17 08:02:45

@Autowired@Resource

2023-02-01 07:15:16

2023-06-07 08:08:43

JVM內(nèi)存模型

2020-07-28 08:59:22

JavahreadLocal面試

2021-12-09 12:22:28

MyBatis流程面試

2016-10-21 09:29:53

嵌入式Linux更新機(jī)制

2021-04-26 17:23:21

JavaCAS原理

2010-03-10 11:55:30

Mocha BSM運(yùn)維管理摩卡軟件

2021-04-21 07:53:13

Android屏幕刷新

2021-09-10 18:47:22

Redis淘汰策略

2021-03-23 07:56:54

JS基礎(chǔ)同步異步編程EventLoop底層

2023-02-03 07:24:49

雙親委派模型
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)