av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

從Javascript事件循環(huán)看Vue.nextTick的原理和執(zhí)行機(jī)制

拋磚引玉

創(chuàng)新互聯(lián)建站專注于企業(yè)成都全網(wǎng)營(yíng)銷、網(wǎng)站重做改版、四平網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、HTML5建站、商城建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)公司、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為四平等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

Vue 的特點(diǎn)之一就是響應(yīng)式,但是有些時(shí)候數(shù)據(jù)更新了,我們看到頁(yè)面上的 DOM 并沒(méi)有立刻更新。如果我們需要在 DOM 更新之后再執(zhí)行一段代碼時(shí),可以借助 nextTick 實(shí)現(xiàn)。

我們先來(lái)看一個(gè)例子

 
 
 
  1. export default {
  2.   data() {
  3.     return {
  4.       msg: 0
  5.     }
  6.   },
  7.   mounted() {
  8.     this.msg = 1
  9.     this.msg = 2
  10.     this.msg = 3
  11.   },
  12.   watch: {
  13.     msg() {
  14.       console.log(this.msg)
  15.     }
  16.   }
  17. }

這里的結(jié)果是只輸出一個(gè) 3,而非依次輸出 1,2,3。這是為什么呢?

vue 的官方文檔是這樣解釋的:

Vue 異步執(zhí)行 DOM 更新。只要觀察到數(shù)據(jù)變化,Vue 將開啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個(gè)watcher 被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和 DOM 操作上非常重要。然后,在下一個(gè)的事件循環(huán)“tick”中,Vue 刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作。Vue 在內(nèi)部嘗試對(duì)異步隊(duì)列使用原生的Promise.then和 MessageChannel,如果執(zhí)行環(huán)境不支持,會(huì)采用setTimeout(fn, 0)代替。

假如有這樣一種情況,mounted鉤子函數(shù)下一個(gè)變量 a 的值會(huì)被++循環(huán)執(zhí)行 1000 次。每次++時(shí),都會(huì)根據(jù)響應(yīng)式觸發(fā)setter->Dep->Watcher->update->run。如果這時(shí)候沒(méi)有異步更新視圖,那么每次++都會(huì)直接操作 DOM 一次,這是非常消耗性能的。所以 Vue 實(shí)現(xiàn)了一個(gè)queue隊(duì)列,在下一個(gè) Tick(或者是當(dāng)前 Tick 的微任務(wù)階段)的時(shí)候會(huì)統(tǒng)一執(zhí)行queue中Watcher的run。同時(shí),擁有相同 id 的Watcher不會(huì)被重復(fù)加入到該queue中去,所以不會(huì)執(zhí)行 1000 次Watcher的run。最終的結(jié)果是直接把 a 的值從 1 變成 1000,大大提升了性能。

在 vue 中,數(shù)據(jù)監(jiān)測(cè)都是通過(guò)Object.defineProperty來(lái)重寫里面的 set 和 get 方法實(shí)現(xiàn)的,vue 更新 DOM 是異步的,每當(dāng)觀察到數(shù)據(jù)變化時(shí),vue 就開始一個(gè)隊(duì)列,將同一事件循環(huán)內(nèi)所有的數(shù)據(jù)變化緩存起來(lái),等到下一次 eventLoop,將會(huì)把隊(duì)列清空,進(jìn)行 DOM 更新。

想要了解 vue.nextTick 的執(zhí)行機(jī)制,我們先來(lái)了解一下 javascript 的事件循環(huán)。

js 事件循環(huán)

js 的任務(wù)隊(duì)列分為同步任務(wù)和異步任務(wù),所有的同步任務(wù)都是在主線程里執(zhí)行的。異步任務(wù)可能會(huì)在 macrotask 或者 microtask 里面,異步任務(wù)進(jìn)入 Event Table 并注冊(cè)函數(shù)。當(dāng)指定的事情完成時(shí),Event Table 會(huì)將這個(gè)函數(shù)移入 Event Queue。主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會(huì)去 Event Queue 讀取對(duì)應(yīng)的函數(shù),進(jìn)入主線程執(zhí)行。上述過(guò)程會(huì)不斷重復(fù),也就是常說(shuō)的 Event Loop(事件循環(huán))。

1. macro-task(宏任務(wù)):

每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)。瀏覽器為了能夠使得 js 內(nèi)部(macro)task與 DOM 任務(wù)能夠有序執(zhí)行,會(huì)在一個(gè)(macro)task執(zhí)行結(jié)束后,在下一個(gè)(macro)task執(zhí)行開始前,對(duì)頁(yè)面進(jìn)行重新渲染。宏任務(wù)主要包含:

  • script(整體代碼)
  • setTimeout / setInterval
  • setImmediate(Node.js 環(huán)境)
  • I/O
  • UI render
  • postMessage
  • MessageChannel

2. micro-task(微任務(wù)):

可以理解是在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)。也就是說(shuō),在當(dāng)前 task 任務(wù)后,下一個(gè) task 之前,在渲染之前。所以它的響應(yīng)速度相比 setTimeout(setTimeout 是 task)會(huì)更快,因?yàn)闊o(wú)需等渲染。也就是說(shuō),在某一個(gè) macrotask 執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有 microtask 都執(zhí)行完畢(在渲染前)。microtask 主要包含:

  • process.nextTick(Node.js 環(huán)境)
  • Promise
  • Async/Await
  • MutationObserver(html5 新特性)

3. 小結(jié)

  • 先執(zhí)行主線程
  • 遇到宏隊(duì)列(macrotask)放到宏隊(duì)列(macrotask)
  • 遇到微隊(duì)列(microtask)放到微隊(duì)列(microtask)
  • 主線程執(zhí)行完畢
  • 執(zhí)行微隊(duì)列(microtask),微隊(duì)列(microtask)執(zhí)行完畢
  • 執(zhí)行一次宏隊(duì)列(macrotask)中的一個(gè)任務(wù),執(zhí)行完畢
  • 執(zhí)行微隊(duì)列(microtask),執(zhí)行完畢
  • 依次循環(huán)。。。

Vue.nextTick 源碼

vue 是采用雙向數(shù)據(jù)綁定的方法驅(qū)動(dòng)數(shù)據(jù)更新的,雖然這樣能避免直接操作 DOM,提高了性能,但有時(shí)我們也不可避免需要操作 DOM,這時(shí)就該 Vue.nextTick(callback)出場(chǎng)了,它接受一個(gè)回調(diào)函數(shù),在 DOM 更新完成后,這個(gè)回調(diào)函數(shù)就會(huì)被調(diào)用。不管是 vue.nextTick 還是vue.prototype.\$nextTick 都是直接用的nextTick這個(gè)閉包函數(shù)。

 
 
 
  1. export const nextTick = (function () {
  2.   const callbacks = []
  3.   let pending = false
  4.   let timerFunc
  5.   function nextTickHandler () {
  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.  ...
  14. })()

使用數(shù)組callbacks保存回調(diào)函數(shù),pending表示當(dāng)前狀態(tài),使用函數(shù)nextTickHandler 來(lái)執(zhí)行回調(diào)隊(duì)列。在該方法內(nèi),先通過(guò)slice(0)保存了回調(diào)隊(duì)列的一個(gè)副本,通過(guò)設(shè)置 callbacks.length = 0清空回調(diào)隊(duì)列,最后使用循環(huán)執(zhí)行在副本里的所有函數(shù)。

 
 
 
  1. if (typeof Promise !== 'undefined' && isNative(Promise)) {
  2.   var p = Promise.resolve()
  3.   var logError = err => {
  4.     console.error(err)
  5.   }
  6.   timerFunc = () => {
  7.     p.then(nextTickHandler).catch(logError)
  8.     if (isIOS) setTimeout(noop)
  9.   }
  10. } else if (typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]')) {
  11.   var counter = 1
  12.   var observer = new MutationObserver(nextTickHandler)
  13.   var textNode = document.createTextNode(String(counter))
  14.   observer.observe(textNode, {
  15.     characterData: true
  16.   })
  17.   timerFunc = () => {
  18.     counter = (counter + 1) % 2
  19.     textNode.data = String(counter)
  20.   }
  21. } else {
  22.   timeFunc = () => {

隊(duì)列控制的最佳選擇是microtask,而microtask的最佳選擇是Promise。但如果當(dāng)前環(huán)境不支持 Promise,就檢測(cè)到瀏覽器是否支持 MO,是則創(chuàng)建一個(gè)文本節(jié)點(diǎn),監(jiān)聽(tīng)這個(gè)文本節(jié)點(diǎn)的改動(dòng)事件,以此來(lái)觸發(fā)nextTickHandler(也就是 DOM 更新完畢回調(diào))的執(zhí)行。此外因?yàn)榧嫒菪詥?wèn)題,vue 不得不做了microtask向macrotask 的降級(jí)方案。

為讓這個(gè)回調(diào)函數(shù)延遲執(zhí)行,vue 優(yōu)先用promise來(lái)實(shí)現(xiàn),其次是 html5 的 MutationObserver,然后是setTimeout。前兩者屬于microtask,后一個(gè)屬于 macrotask。下面來(lái)看最后一部分。

 
 
 
  1. return function queueNextTick(cb?: Function, ctx?: Object) {
  2.   let _resolve
  3.   callbacks.push(() => {
  4.     if (cb) cb.call(ctx)
  5.     if (_resolve) _resolve(ctx)
  6.   })
  7.   if (!pending) {
  8.     pending = true
  9.     timerFunc()
  10.   }
  11.   if (!cb && typeof Promise !== 'undefined') {
  12.     return new Promise(resolve => {
  13.       _resolve = resolve
  14.     })
  15.   }
  16. }

這就是我們真正調(diào)用的nextTick函數(shù),在一個(gè)event loop內(nèi)它會(huì)將調(diào)用 nextTick的cb 回調(diào)函數(shù)都放入 callbacks 中,pending 用于判斷是否有隊(duì)列正在執(zhí)行回調(diào),例如有可能在 nextTick 中還有一個(gè) nextTick,此時(shí)就應(yīng)該屬于下一個(gè)循環(huán)了。最后幾行代碼是 promise 化,可以將 nextTick 按照 promise 方式去書寫(暫且用的較少)。

應(yīng)用場(chǎng)景

場(chǎng)景一、點(diǎn)擊按鈕顯示原本以 v-show = false 隱藏起來(lái)的輸入框,并獲取焦點(diǎn)。

 
 
 
  1. showInput(){
  2.   this.showit = true
  3.   document.getElementById("keywords").focus()
  4. }

以上的寫法在第一個(gè) tick 里,因?yàn)楂@取不到輸入框,自然也獲取不到焦點(diǎn)。如果我們改成以下的寫法,在 DOM 更新后就可以獲取到輸入框焦點(diǎn)了。

 
 
 
  1. showsou(){
  2.   this.showit = true
  3.   this.$nextTick(function () {
  4.     // DOM 更新了
  5.     document.getElementById("keywords").focus()
  6.   })
  7. }

場(chǎng)景二、獲取元素屬,點(diǎn)擊獲取元素寬度。

 
 
 
  1.   {{ message }}

  2.   獲取p元素寬度
  • getMyWidth() {
  •   this.showMe = true;
  •   thisthis.message = this.$refs.myWidth.offsetWidth;
  •   //報(bào)錯(cuò) TypeError: this.$refs.myWidth is undefined
  •   this.$nextTick(()=>{
  •       //dom元素更新后執(zhí)行,此時(shí)能拿到p元素的屬性
  •     thisthis.message = this.$refs.myWidth.offsetWidth;
  •   })
  • }

  • 當(dāng)前文章:從Javascript事件循環(huán)看Vue.nextTick的原理和執(zhí)行機(jī)制
    本文網(wǎng)址:http://uogjgqi.cn/article/coeoipe.html
    掃二維碼與項(xiàng)目經(jīng)理溝通

    我們?cè)谖⑿派?4小時(shí)期待你的聲音

    解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流