淺談Vue.nextTick 的實(shí)現(xiàn)方法
這是一篇繼event loop和MicroTask 后的vue.nextTick API實(shí)現(xiàn)的源碼解析。
預(yù)熱,寫(xiě)一個(gè)sleep函數(shù)
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函數(shù)
async 函數(shù)進(jìn)行await PromiseFn()時(shí)函數(shù)執(zhí)行是暫停的,我們也知道現(xiàn)在這個(gè)PromiseFn是在microTask內(nèi)執(zhí)行。當(dāng)microTask沒(méi)執(zhí)行完畢時(shí),后面的macroTask是不會(huì)執(zhí)行的,我們也就通過(guò)microTask在event loop的特性實(shí)現(xiàn)了一個(gè)sleep函數(shù),阻止了console.log的執(zhí)行
流程
1執(zhí)行console.log('start')
2執(zhí)行await 執(zhí)行暫停,等待await函數(shù)后的PromiseFn在microTask執(zhí)行完畢
3在sleep函數(shù)內(nèi),延遲ms返回
4返回resolve后執(zhí)行console.log('end')
nextTick API
vue中nextTick的使用方法
vue.nextTick(() => { // todo... })
了解用法后看一下源碼
const nextTick = (function () { const callbacks = [] let pending = false let timerFunc // 定時(shí)函數(shù) function nextTickHandler () { pending = false const copies = callbacks.slice(0) // 復(fù)制 callbacks.length = 0 // 清空 for (let i = 0; i < copies.length; i++) { copies[i]() // 逐個(gè)執(zhí)行 } } if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 重點(diǎn) } } else if ('!isIE MutationObserver') { var counter = 1 var observer = new MutationObserver(nextTickHandler) // 重點(diǎn) 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) // 重點(diǎn) } } 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 }) } } })() // 自執(zhí)行函數(shù)
大致看一下源碼可以了解到nextTick api是一個(gè)自執(zhí)行函數(shù)
既然是自執(zhí)行函數(shù),直接看它的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 }) } }
只關(guān)注主流程queueNextTick函數(shù)把我們傳入的() => { // todo... } 推入了callbacks內(nèi)
if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 重點(diǎn) } } else if ('!isIE MutationObserver') { var counter = 1 var observer = new MutationObserver(nextTickHandler) // 重點(diǎn) 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) // 重點(diǎn) } }
這一段我們可以看到標(biāo)注的三個(gè)點(diǎn)表明在不同瀏覽器環(huán)境下使用Promise, MutationObserver或setTimeout(fn, 0) 來(lái)執(zhí)行nextTickHandler
function nextTickHandler () { pending = false const copies = callbacks.slice(0) // 復(fù)制 callbacks.length = 0 // 清空 for (let i = 0; i < copies.length; i++) { copies[i]() // 逐個(gè)執(zhí)行 } }
nextTickHandler就是把我們之前放入callbacks的 () => { // todo... } 在當(dāng)前tasks內(nèi)執(zhí)行。
寫(xiě)一個(gè)簡(jiǎn)單的nextTick
源碼可能比較繞,我們自己寫(xiě)一段簡(jiǎn)單的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() // 執(zhí)行timerFunc,返回到是一個(gè)Promise } })() simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
我們可以從這里看出nextTick的原理就是返回出一個(gè)Promise,而我們todo的代碼在這個(gè)Promise中執(zhí)行,現(xiàn)在我們還可以繼續(xù)簡(jiǎn)化
const simpleNextTick = (function () { return function queueNextTick (cb) { timerFunc = () => { return Promise.resolve().then(() => { cb() }) } timerFunc() } })() simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
直接寫(xiě)成這樣。
const simpleNextTick = function queueNextTick (cb) { timerFunc = () => { return Promise.resolve().then(() => { cb() }) } timerFunc() } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
這次我們把自執(zhí)行函數(shù)也簡(jiǎn)化掉
const simpleNextTick = function queueNextTick (cb) { return Promise.resolve().then(cb) } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') })
現(xiàn)在我們直接簡(jiǎn)化到最后,現(xiàn)在發(fā)現(xiàn)nextTick最核心的內(nèi)容就是Promise,一個(gè)microtask。
現(xiàn)在我們回到vue的nextTick API官方示例
<div id="example">{{message}}</div> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改數(shù)據(jù) vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
原來(lái)在vue內(nèi)數(shù)據(jù)的更新后dom更新是要在下一個(gè)事件循環(huán)后執(zhí)行的。
nextTick的使用原則主要就是解決單一事件更新數(shù)據(jù)后立即操作dom的場(chǎng)景。
既然我們知道了nextTick核心是利用microTasks,那么我們把簡(jiǎn)化過(guò)的nextTick和開(kāi)頭的sleep函數(shù)對(duì)照一下。
const simpleNextTick = function queueNextTick (cb) { return Promise.resolve().then(cb) } simpleNextTick(() => { setTimeout(console.log, 3000, 'nextTick') // 也可以換成ajax請(qǐng)求 })
function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms) // 也可以換成ajax請(qǐng)求 } async function oneTick (ms) { console.log('start') await sleep(ms) console.log('end') } oneTick(3000)
我們看出nextTick和我么寫(xiě)的oneTick的執(zhí)行結(jié)果是那么的相似。區(qū)別只在于nextTick是把callback包裹一個(gè)Promise返回并執(zhí)行,而oneTick是用await執(zhí)行一個(gè)Promise函數(shù),而這個(gè)Promise有自己包裹的webapi函數(shù)。
那在用ajax請(qǐng)求的時(shí)候我們是不是直接這樣使用axios可以返回Promise的庫(kù)
async function getData () { const data = await axios.get(url) // 操作data的數(shù)據(jù)來(lái)改變dom return data }
這樣也可以達(dá)到同nextTick同樣的作用
最后我們也可以從源碼中看出,當(dāng)瀏覽器環(huán)境不支持Promise時(shí)可以使用MutationObserver或setTimeout(cb, 0) 來(lái)達(dá)到同樣的效果。但最終的核心是microTask
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue中watch監(jiān)聽(tīng)不到變化的解決
本文主要介紹了vue中watch監(jiān)聽(tīng)不到變化的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01公共Hooks封裝useTableData表格數(shù)據(jù)實(shí)例
這篇文章主要為大家介紹了公共Hooks封裝useTableData表格數(shù)據(jù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Vue.extend和VueComponent的關(guān)系源碼解析
這篇文章主要為大家詳解了Vue.extend和VueComponent的關(guān)系源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02純JS如何實(shí)現(xiàn)vue.js下的雙向綁定功能
對(duì)于vue下的雙向綁定功能,個(gè)人理解為在處理邏輯的過(guò)程中緩存了大量的node對(duì)象,node對(duì)象可以是html標(biāo)簽、文本內(nèi)容。既然選擇了緩存這些對(duì)象,那么在用的過(guò)程中哪里需要改變就把node拿出來(lái),進(jìn)行標(biāo)簽屬性的變更或者文本內(nèi)容的修改。本文主要講了如何實(shí)現(xiàn)雙向綁定2021-06-06