新聞中心
這是一篇繼event loop和MicroTask 后的vue.nextTick API實(shí)現(xiàn)的源碼解析。

為茂名等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及茂名網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站制作、茂名網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
預(yù)熱,寫一個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ù)執(zhí)行是暫停的,我們也知道現(xiàn)在這個PromiseFn是在microTask內(nèi)執(zhí)行。當(dāng)microTask沒執(zhí)行完畢時,后面的macroTask是不會執(zhí)行的,我們也就通過microTask在event loop的特性實(shí)現(xiàn)了一個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ù)
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0) // 復(fù)制
callbacks.length = 0 // 清空
for (let i = 0; i < copies.length; i++) {
copies[i]() // 逐個執(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是一個自執(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)注的三個點(diǎn)表明在不同瀏覽器環(huán)境下使用Promise, MutationObserver或setTimeout(fn, 0) 來執(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]() // 逐個執(zhí)行
}
}
nextTickHandler就是把我們之前放入callbacks的 () => { // todo... } 在當(dāng)前tasks內(nèi)執(zhí)行。
寫一個簡單的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() // 執(zhí)行timerFunc,返回到是一個Promise
}
})()
simpleNextTick(() => {
setTimeout(console.log, 3000, 'nextTick')
})
我們可以從這里看出nextTick的原理就是返回出一個Promise,而我們todo的代碼在這個Promise中執(zhí)行,現(xiàn)在我們還可以繼續(xù)簡化
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')
})
這次我們把自執(zhí)行函數(shù)也簡化掉
const simpleNextTick = function queueNextTick (cb) {
return Promise.resolve().then(cb)
}
simpleNextTick(() => {
setTimeout(console.log, 3000, 'nextTick')
})
現(xiàn)在我們直接簡化到最后,現(xiàn)在發(fā)現(xiàn)nextTick最核心的內(nèi)容就是Promise,一個microtask。
現(xiàn)在我們回到vue的nextTick API官方示例
{{message}}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 })
原來在vue內(nèi)數(shù)據(jù)的更新后dom更新是要在下一個事件循環(huán)后執(zhí)行的。
nextTick的使用原則主要就是解決單一事件更新數(shù)據(jù)后立即操作dom的場景。
既然我們知道了nextTick核心是利用microTasks,那么我們把簡化過的nextTick和開頭的sleep函數(shù)對照一下。
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的執(zhí)行結(jié)果是那么的相似。區(qū)別只在于nextTick是把callback包裹一個Promise返回并執(zhí)行,而oneTick是用await執(zhí)行一個Promise函數(shù),而這個Promise有自己包裹的webapi函數(shù)。
那在用ajax請求的時候我們是不是直接這樣使用axios可以返回Promise的庫
async function getData () {
const data = await axios.get(url)
// 操作data的數(shù)據(jù)來改變dom
return data
}這樣也可以達(dá)到同nextTick同樣的作用
最后我們也可以從源碼中看出,當(dāng)瀏覽器環(huán)境不支持Promise時可以使用MutationObserver或setTimeout(cb, 0) 來達(dá)到同樣的效果。但最終的核心是microTask
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享標(biāo)題:淺談Vue.nextTick的實(shí)現(xiàn)方法
文章位置:http://www.dlmjj.cn/article/ppijeh.html


咨詢
建站咨詢
