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

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

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

拋磚引玉

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

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

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

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

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

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

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

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

js 事件循環(huán)

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

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

每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù)(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)。瀏覽器為了能夠使得 js 內(nèi)部(macro)task與 DOM 任務(wù)能夠有序執(zhí)行,會在一個(macro)task執(zhí)行結(jié)束后,在下一個(macro)task執(zhí)行開始前,對頁面進行重新渲染。宏任務(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ù)。也就是說,在當(dāng)前 task 任務(wù)后,下一個 task 之前,在渲染之前。所以它的響應(yīng)速度相比 setTimeout(setTimeout 是 task)會更快,因為無需等渲染。也就是說,在某一個 macrotask 執(zhí)行完后,就會將在它執(zhí)行期間產(chǎn)生的所有 microtask 都執(zhí)行完畢(在渲染前)。microtask 主要包含:

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

3. 小結(jié)

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

Vue.nextTick 源碼

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

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

使用數(shù)組callbacks保存回調(diào)函數(shù),pending表示當(dāng)前狀態(tài),使用函數(shù)nextTickHandler 來執(zhí)行回調(diào)隊列。在該方法內(nèi),先通過slice(0)保存了回調(diào)隊列的一個副本,通過設(shè)置 callbacks.length = 0清空回調(diào)隊列,最后使用循環(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。但如果當(dāng)前環(huán)境不支持 Promise,就檢測到瀏覽器是否支持 MO,是則創(chuàng)建一個文本節(jié)點,監(jiān)聽這個文本節(jié)點的改動事件,以此來觸發(fā)nextTickHandler(也就是 DOM 更新完畢回調(diào))的執(zhí)行。此外因為兼容性問題,vue 不得不做了microtask向macrotask 的降級方案。

為讓這個回調(diào)函數(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.   } 

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

應(yīng)用場景

場景一、點擊按鈕顯示原本以 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元素更新后執(zhí)行,此時能拿到p元素的屬性 
  12.     thisthis.message = this.$refs.myWidth.offsetWidth; 
  13.   }) 

 

責(zé)任編輯:趙寧寧 來源: 前端先鋒隊
相關(guān)推薦

2020-09-21 14:35:20

VuenextTick前端

2017-02-09 15:15:54

Chrome瀏覽器

2017-09-12 09:50:08

JavaScriptEvent LoopVue.js

2024-08-26 14:52:58

JavaScript循環(huán)機制

2016-09-06 21:23:25

JavaScriptnode異步

2021-10-15 09:56:10

JavaScript異步編程

2020-12-29 08:21:03

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

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微任務(wù)

2017-06-29 09:15:36

推薦算法策略

2024-09-20 05:46:00

2025-05-09 01:30:00

JavaScript事件循環(huán)基石

2022-04-25 09:03:16

JavaScript代碼

2009-03-17 15:36:29

JavaScript循環(huán)事件

2021-12-08 07:55:41

EventLoop瀏覽器事件

2022-09-19 19:51:30

ReactuseEffect

2010-01-28 10:29:44

點贊
收藏

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

主站蜘蛛池模板: 日韩在线| 欧美在线视频免费 | 中文字幕视频在线看 | 亚洲精品久久久久中文字幕欢迎你 | av片在线播放 | 青青草视频网站 | 中文字幕一区在线观看视频 | 国产精品久久精品 | 91久久| 在线一区二区三区 | 精品久久久久久久久亚洲 | 热99在线 | 一区二区三区日韩精品 | 黄片毛片免费观看 | 51ⅴ精品国产91久久久久久 | 国产免费观看一区 | japan25hdxxxx日本 做a的各种视频 | 祝你幸福电影在线观看 | 天天干 夜夜操 | 国产探花在线观看视频 | 欧美在线综合 | 一区不卡在线观看 | 欧美日韩国产精品一区 | 狠狠操av | 超碰免费在线观看 | 久久国产精品一区 | 黑人精品欧美一区二区蜜桃 | 亚洲高清视频一区 | 精品视频在线观看 | 蜜臀久久99精品久久久久野外 | 国产精品久久久久久妇女6080 | 在线播放精品视频 | 91大神在线资源观看无广告 | 欧美成人免费在线视频 | 在线国产一区二区 | 国产一区二区在线视频 | 日屁网站 | 国产成人精品综合 | 一级黄色片在线免费观看 | 精品一区av | 三级在线免费观看 |