日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從Javascript事件循環(huán)看Vue.nextTick的原理和執(zhí)行機制

拋磚引玉

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

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

我們先來看一個例子

 
 
 
  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. }

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

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

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

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

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

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

js 事件循環(huán)

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

1. macro-task(宏任務):

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

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

2. micro-task(微任務):

可以理解是在當前 task 執(zhí)行結束后立即執(zhí)行的任務。也就是說,在當前 task 任務后,下一個 task 之前,在渲染之前。所以它的響應速度相比 setTimeout(setTimeout 是 task)會更快,因為無需等渲染。也就是說,在某一個 macrotask 執(zhí)行完后,就會將在它執(zhí)行期間產生的所有 microtask 都執(zhí)行完畢(在渲染前)。microtask 主要包含:

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

3. 小結

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

Vue.nextTick 源碼

vue 是采用雙向數(shù)據(jù)綁定的方法驅動數(shù)據(jù)更新的,雖然這樣能避免直接操作 DOM,提高了性能,但有時我們也不可避免需要操作 DOM,這時就該 Vue.nextTick(callback)出場了,它接受一個回調函數(shù),在 DOM 更新完成后,這個回調函數(shù)就會被調用。不管是 vue.nextTick 還是vue.prototype.\$nextTick 都是直接用的nextTick這個閉包函數(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保存回調函數(shù),pending表示當前狀態(tài),使用函數(shù)nextTickHandler 來執(zhí)行回調隊列。在該方法內,先通過slice(0)保存了回調隊列的一個副本,通過設置 callbacks.length = 0清空回調隊列,最后使用循環(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 = () => {

隊列控制的最佳選擇是microtask,而microtask的最佳選擇是Promise。但如果當前環(huán)境不支持 Promise,就檢測到瀏覽器是否支持 MO,是則創(chuàng)建一個文本節(jié)點,監(jiān)聽這個文本節(jié)點的改動事件,以此來觸發(fā)nextTickHandler(也就是 DOM 更新完畢回調)的執(zhí)行。此外因為兼容性問題,vue 不得不做了microtask向macrotask 的降級方案。

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

 
 
 
  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. }

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

應用場景

場景一、點擊按鈕顯示原本以 v-show = false 隱藏起來的輸入框,并獲取焦點。

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

以上的寫法在第一個 tick 里,因為獲取不到輸入框,自然也獲取不到焦點。如果我們改成以下的寫法,在 DOM 更新后就可以獲取到輸入框焦點了。

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

場景二、獲取元素屬,點擊獲取元素寬度。

 
 
 
  1.   {{ message }}

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

  • 網(wǎng)頁標題:從Javascript事件循環(huán)看Vue.nextTick的原理和執(zhí)行機制
    網(wǎng)站網(wǎng)址:http://www.5511xx.com/article/coeoipe.html