本文主要介紹了Vue.nextTick 的實作方法,小編覺得蠻不錯的,現在分享給大家,也給大家做個參考。一起跟著小編過來看看吧,希望能幫助大家。
這是一篇繼event loop和MicroTask 後的vue.nextTick API實作的原始碼解析。
預熱,寫一個sleep函數
function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms) } async function oneTick (ms) { console.log('start') await sleep(ms) console.log('end') } oneTick(3000)
解釋下sleep函數
async 函數進行await PromiseFn()時函數執行是暫停的,我們也知道現在這個PromiseFn是在microTask內執行。當microTask沒執行完畢時,後面的macroTask是不會執行的,我們也就透過microTask在event loop的特性實作了一個sleep函數,阻止了console.log的執行
#流程
1執行console.log('start')
2執行await 執行暫停,等待await函數後的PromiseFn在microTask執行完畢
3在sleep函數內,延遲ms回傳
4返回resolve後執行console.log('end')
nextTick API
vue中nextTick的使用方法
vue.nextTick(() => { // todo... })
了解用法後看一下源碼
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]() // 逐个执行 } } if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 重点 } } else if ('!isIE MutationObserver') { var counter = 1 var observer = new MutationObserver(nextTickHandler) // 重点 var textNode = document.createTextNode(string(conter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { timerFunc = () => { setTimeout(nextTickHandler, 0) // 重点 } } return function queueNextTick (cb, ctx) { // api的使用方式 let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { err } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve =resolve }) } } })() // 自执行函数
大致看一下原始碼可以了解到nextTick api是一個自執行函數
現在是自執行函數,直接看它的return類型,return function queueNextTick (cb, ctx) {...}
return function queueNextTick (cb, ctx) { // api的使用方式 let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { err } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve =resolve }) } }
只專注於主流程queueNextTick函數把我們傳入的() => { // todo... } 推入了callbacks內
if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 重点 } } else if ('!isIE MutationObserver') { var counter = 1 var observer = new MutationObserver(nextTickHandler) // 重点 var textNode = document.createTextNode(string(conter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { timerFunc = () => { setTimeout(nextTickHandler, 0) // 重点 } }
這段我們可以看到標註的三個點表示在不同瀏覽器環境下使用Promise, MutationObserver或setTimeout(fn, 0) 來執行nextTickHandler
function nextTickHandler () { pending = false const copies = callbacks.slice(0) // 复制 callbacks.length = 0 // 清空 for (let i = 0; i < copies.length; i++) { copies[i]() // 逐个执行 } }
nextTickHandler就是把我們之前放入callbacks的() => { // todo... } 在目前tasks內執行。
寫一個簡單的nextTick
原始碼可能比較繞,我們自己寫一段簡單的nextTick
const simpleNextTick = (function () { let callbacks = [] let timerFunc return function queueNextTick (cb) { callbacks.push(() => { // 给callbacks 推入cb() cb() }) timerFunc = () => { return Promise.resolve().then(() => { const fn = callbacks.shift() fn() }) } timerFunc() // 执行timerFunc,返回到是一个Promise } })() simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
我們可以從這裡看出nextTick的原理就是回傳一個Promise,而我們todo的程式碼在這個Promise中執行,現在我們還可以繼續簡化
const simpleNextTick = (function () { return function queueNextTick (cb) { timerFunc = () => { return Promise.resolve().then(() => { cb() }) } timerFunc() } })() simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
直接寫成這樣。
const simpleNextTick = function queueNextTick (cb) { timerFunc = () => { return Promise.resolve().then(() => { cb() }) } timerFunc() } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
這次我們把自執行函數也簡化掉
const simpleNextTick = function queueNextTick (cb) { return Promise.resolve().then(cb) } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
現在我們直接簡化到最後,現在發現nextTick最核心的內容就是Promise,一個microtask。
現在我們回到vue的nextTick API官方範例
<p id="example">{{message}}</p> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改数据 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
原來在vue內資料的更新後dom更新是要在下一個事件循環後執行的。
nextTick的使用原則主要是解決單一事件更新資料後立即操作dom的場景。
既然我們知道了nextTick核心是利用microTasks,那麼我們把簡化過的nextTick和開頭的sleep函數對照一下。
const simpleNextTick = function queueNextTick (cb) { return Promise.resolve().then(cb) } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') // 也可以换成ajax请求 })
function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms) // 也可以换成ajax请求 } async function oneTick (ms) { console.log('start') await sleep(ms) console.log('end') } oneTick(3000)
我們看出nextTick和我麼寫的oneTick的執行結果是那麼的相似。差異只在於nextTick是把callback包裹一個Promise回傳並執行,而oneTick是用await執行一個Promise函數,而這個Promise有自己包裹的webapi函數。
那在用ajax請求的時候我們是不是直接這樣使用axios可以返回Promise的庫
async function getData () { const data = await axios.get(url) // 操作data的数据来改变dom return data }
這樣也可以達到同nextTick同樣的作用
最後我們也可以從原始碼中看出,當瀏覽器環境不支援Promise時可以使用MutationObserver或setTimeout(cb, 0) 來達到相同的效果。但最終的核心是microTask
相關推薦:
詳解Vue + Vuex 使用vm.$nextTick實例詳解
以上是Vue.nextTick 的實作方法詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!