由於Vue是異步驅動視圖更新數據的,即當咱們在事件中修改數據時,視圖並不會即時的更新,而是等在同一事件循環的全部數據變化完成後,再進行視圖更新。相似於Event Loop事件循環機制。javascript
首先咱們看下官網給出的介紹:html
❝❞
{Function} [callback]
{Object} [context]
vue
// 修改數據
vm.msg = 'Hello' // 當咱們在這裏調用DOM的數據時,它其實尚未更新 Vue.nextTick(function () { // DOM 更新了 }) // 2.1.0新增 Promise用法 Vue.nextTick() .then(function () { // 此時DOM已經更新 }) 複製代碼
❝2.1.0 起新增:若是沒有提供回調且在支持 Promise 的環境中,則返回一個 Promise。請注意 Vue 不自帶 Promise 的 polyfill,因此若是你的目標瀏覽器不原生支持 Promise (IE:大家都看我幹嗎),你得本身提供 polyfill。java
❞
首先,Vue實現響應式並非在數據改變後就當即更新DOM,而是在一次事件循環的全部數據變化後再異步執行DOM更新.ios
有關異步以及事件循環,能夠看下我以前寫過的一篇關於文章JS中的異步web
若是不想去詳細瞭解,這邊我就簡單總結一下事件循環:瀏覽器
同步代碼的執行 => 查找異步隊列,進入執行棧,執行Callback1[事件循環1] => 查找異步隊列,進入執行棧,執行Callback2[事件循環2] => .....app
❝即每一個異步的Callback都會再獨立造成一次事件循環 因此咱們能夠退出nextTick的觸發時機 一次事件循環中的代碼執行完畢 => DOM更新 => 觸發nextTick的回調 => 進入下一循環dom
❞
可能只憑一些概念性的講解仍是沒法對nextTick機制有很清晰的瞭解,仍是上個示例來了解一下吧。異步
<template>
<div class="app"> <div ref="contentDiv">{{content}}</div> <div>在nextTick執行前獲取內容:{{content1}}</div> <div>在nextTick執行以後獲取內容:{{content2}}</div> <div>在nextTick執行前獲取內容:{{content3}}</div> </div> </template> <script> export default { name:'App', data: { content: 'Before NextTick', content1: '', content2: '', content3: '' }, methods: { changeContent () { this.content = 'After NextTick' // 在此處更新content的數據 this.content1 = this.$refs.contentDiv.innerHTML //獲取DOM中的數據 this.$nextTick(() => { // 在nextTick的回調中獲取DOM中的數據 this.content2 = this.$refs.contentDiv.innerHTML }) this.content3 = this.$refs.contentDiv.innerHTML } }, mount () { this.changeContent() } } </script> 複製代碼
當咱們打開頁面後咱們能夠發現結果爲:
After NextTick 在nextTick執行前獲取內容:Before NextTick 在nextTick執行以後獲取內容:After NextTick 在nextTick執行前獲取內容:Before NextTick 複製代碼
因此咱們能夠知道,雖然content1
和content3
得到內容的語句是寫在content
數據改變語句以後的,但他們屬於同一個事件循環中,因此content1
和content3
獲取的仍是 'Before NextTick' ,而content2
得到內容的語句寫在nextTick
的回調中,在DOM更新以後再執行,因此獲取的是更新後的 'After NextTick'。
下面是一些nextTick的主要應用場景
當在created()
生命週期中直接執行DOM操做是不可取的,由於此時的DOM並未進行任何的渲染。因此解決辦法是將DOM操做寫進Vue.nextTick()
的回調函數中。或者是將操做放入mounted()
鉤子函數中
在咱們更新數據後,若是還有操做要根據更新數據後的DOM結構進行,那麼咱們應當將這部分操做放入**Vue.nextTick()**回調函數中
這部分的詳細緣由在Vue的官方文檔中解釋的很是清晰:
❝可能你尚未注意到,Vue 異步執行 DOM 更新。只要觀察到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據改變。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做上很是重要。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue 在內部嘗試對異步隊列使用原生的
❞Promise.then
和MessageChannel
,若是執行環境不支持,會採用setTimeout(fn, 0)
代替。 例如,當你設置vm.someData = 'new value'
,該組件不會當即從新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個「tick」更新。多數狀況咱們不須要關心這個過程,可是若是你想在 DOM 狀態更新後作點什麼,這就可能會有些棘手。雖然 Vue.js 一般鼓勵開發人員沿着「數據驅動」的方式思考,避免直接接觸 DOM,可是有時咱們確實要這麼作。爲了在數據變化以後等待 Vue 完成更新 DOM ,能夠在數據變化以後當即使用Vue.nextTick(callback)
。這樣回調函數在 DOM 更新完成後就會調用。
export const nextTick = (function () {
// 存放全部的回調函數 const callbacks = [] // 是否正在執行回調函數的標誌 let pending = false // 觸發執行回調函數 let timerFunc // 處理回調函數 function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { // 執行回調函數 copies[i]() } } // nextTick行爲利用了微任務隊列 // 它能夠經過原生Promise或者MutationObserver實現 // MutationObserver已經有了普遍的瀏覽器支持,然而他仍然在UIWebView在ios系統9.3.3以上的 // 系統有嚴重的Bug,問題發生在咱們觸摸事件的觸發時。 // 它會在咱們觸發一段時間後徹底中止,因此原生Promise是有效能夠利用的,咱們會使用它: /* istanbul ignore if */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 在有問題的 UIWebViews 中,Promise.then 方法不會徹底的中止,但它可能會在一個 // 奇怪的狀態卡住當咱們把回調函數推入一個微任務隊列可是這個隊列並非在沖洗中,知道 // 瀏覽器須要作一些其餘的任務時,例如:執行一個定時函數。所以咱們能夠"強制"微任務隊 // 列被沖洗經過加入一個空的定時函數 if (isIOS) setTimeout(noop) } } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // 使用MutationObserver當Promise不可用時, // 例如 PhantomJS, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // 當MutationObserver 和 Promise都不可使用時 // 咱們使用setTimeOut來實現 /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })() 複製代碼
咱們經過源碼能夠知道,timeFunc這個函數起延遲執行的做用,它有三種實現方式
其中Promise
和 setTimeout
咱們都不陌生,下面重點介紹一下MutationObserver
MutationObserver
是HTML5中的新API,是個用來監視DOM變更的接口。他能監聽一個DOM對象上發生的子節點刪除、屬性修改、文本內容修改等等。 調用過程很簡單,可是有點不太尋常:你須要先給他綁回調:
let mo = new MutationObserver(callback)
複製代碼
經過給MutationObserver
的構造函數傳入一個回調,能獲得一個MutationObserver
實例,這個回調就會在MutationObserver
實例監聽到變更時觸發。
這個時候你只是給MutationObserver
實例綁定好了回調,他具體監聽哪一個DOM、監聽節點刪除仍是監聽屬性修改,尚未設置。而調用他的observer
方法就能夠完成這一步:
var domTarget = 你想要監聽的dom節點
mo.observe(domTarget, { characterData: true //說明監聽文本內容的修改。 }) 複製代碼
在nextTick
中 MutationObserver
的做用就以下圖所示。在監聽到DOM更新後,調用回調函數。
本文使用 mdnice 排版