成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從Javascript 事件循環看 Vue.nextTick 的原理和執行機制

開發 前端
Vue 的特點之一就是響應式,但是有些時候數據更新了,我們看到頁面上的 DOM 并沒有立刻更新。如果我們需要在 DOM 更新之后再執行一段代碼時,可以借助 nextTick 實現。

拋磚引玉

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

[[323732]]

我們先來看一個例子

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

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

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

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

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

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

想要了解 vue.nextTick 的執行機制,我們先來了解一下 javascript 的事件循環。

js 事件循環

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

1. macro-task(宏任務):

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

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

2. micro-task(微任務):

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

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

3. 小結

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

Vue.nextTick 源碼

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

  1. export const nextTick = (function () { 
  2.   const callbacks = [] 
  3.   let pending = false 
  4.   let timerFunc 
  5.  
  6.   function nextTickHandler () { 
  7.     pending = false 
  8.     const copies = callbacks.slice(0) 
  9.     callbacks.length = 0 
  10.     for (let i = 0; i < copies.length; i++) { 
  11.       copies[i]() 
  12.     } 
  13.   } 
  14.  ... 
  15. })() 

使用數組callbacks保存回調函數,pending表示當前狀態,使用函數nextTickHandler 來執行回調隊列。在該方法內,先通過slice(0)保存了回調隊列的一個副本,通過設置 callbacks.length = 0清空回調隊列,最后使用循環執行在副本里的所有函數。

  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。但如果當前環境不支持 Promise,就檢測到瀏覽器是否支持 MO,是則創建一個文本節點,監聽這個文本節點的改動事件,以此來觸發nextTickHandler(也就是 DOM 更新完畢回調)的執行。此外因為兼容性問題,vue 不得不做了microtask向macrotask 的降級方案。

為讓這個回調函數延遲執行,vue 優先用promise來實現,其次是 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.   } 

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

應用場景

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

  1. <input id="keywords" v-if="showit"> 
  2.  
  3. showInput(){ 
  4.   this.showit = true 
  5.   document.getElementById("keywords").focus() 

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

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

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

  1. <div id="app"> 
  2.   <p ref="myWidth" v-if="showMe">{{ message }}</p> 
  3.   <button @click="getMyWidth">獲取p元素寬度</button> 
  4. </div> 
  5.  
  6. getMyWidth() { 
  7.   this.showMe = true
  8.   thisthis.message = this.$refs.myWidth.offsetWidth; 
  9.   //報錯 TypeError: this.$refs.myWidth is undefined 
  10.   this.$nextTick(()=>
  11.       //dom元素更新后執行,此時能拿到p元素的屬性 
  12.     thisthis.message = this.$refs.myWidth.offsetWidth; 
  13.   }) 

 

責任編輯:趙寧寧 來源: 前端先鋒隊
相關推薦

2020-09-21 14:35:20

VuenextTick前端

2017-09-12 09:50:08

JavaScriptEvent LoopVue.js

2017-02-09 15:15:54

Chrome瀏覽器

2024-08-26 14:52:58

JavaScript循環機制

2016-09-06 21:23:25

JavaScriptnode異步

2021-10-15 09:56:10

JavaScript異步編程

2020-12-29 08:21:03

JavaScript微任務宏任務

2010-07-16 09:00:20

開源RedOffice紅旗2000

2017-07-27 16:31:11

2015-09-21 14:20:35

2024-06-21 08:32:24

2021-01-18 08:24:51

JavaScriptMicrotask微任務

2017-06-29 09:15:36

推薦算法策略

2024-09-20 05:46:00

2022-04-25 09:03:16

JavaScript代碼

2025-05-09 01:30:00

JavaScript事件循環基石

2009-03-17 15:36:29

JavaScript循環事件

2021-12-08 07:55:41

EventLoop瀏覽器事件

2022-09-19 19:51:30

ReactuseEffect

2018-05-22 09:47:07

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一区二区三区免费网站 | 老司机成人在线 | 国产精品毛片久久久久久久 | 欧美a在线| 99热视 | 国产精品久久精品 | 成人在线精品视频 | 午夜视频在线观看视频 | 色婷婷激情 | 久久久www成人免费精品 | 成人国产网站 | 91色网站 | 久久久久一区二区 | 日韩一级 | 国产午夜在线 | 欧美日韩久久精品 | 综合婷婷 | 久久久久久国产免费视网址 | 99久久免费观看 | 成人免费在线播放视频 | 中文字幕第十一页 | 欧美福利| 国产一区二区三区在线看 | 欧产日产国产精品视频 | 国产精品成人一区 | 亚欧精品 | 亚洲国产日本 | 紧缚调教一区二区三区视频 | 天天色综 | 欧美一区二区网站 | 五月天婷婷激情 | 99这里只有精品视频 | 伊人一区| 亚洲国产精品久久久 | 三级黄色网址 | 伊人伊成久久人综合网站 | 午夜国产一区 | 免费a国产| 亚洲第1页| 日韩精品免费在线 | 欧美国产亚洲一区二区 |