Vue中$nextTick實現源碼解析
正文

先看一個簡單的問題
<template>
<div @click="handleClick" ref="div">{{ text }}</div
</template>
<script>
export default {
data() {
return {
text: 'old'
}
},
methods: {
handleClick() {
this.text = 'new'
console.log(this.$refs.div.innerText)
}
}
}
</script>
此時打印的結果是什么呢?是 'old'。如果想讓它打印 'new',使用 nextTick 稍加改造就可以
this.$nextTick(() => {
console.log(this.$refs.div.innerText)
})
內部實現
但是你想過它內部是怎么實現的么,和我們寫 setTimeout 有什么區(qū)別呢?
因為平時工作使用的是Vue2,所以我就以Vue2的最新版本2.6.14為例進行分析,Vue3的實現應該也是大同小異。
為了方便閱讀我刪掉了注釋,只關注最重要的實現
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
先大概掃一遍可知 $nextTick 主要是通過微任務來實現的,其實在2.5版本中,是采用宏任務與微任務相結合的方式實現的,但因為在渲染和事件處理中一些比較怪異的行為(感興趣的話可以看下issue),所以最終統(tǒng)一采用了微任務。
先看第一塊:
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
}
如果可以使用 Promise ,就采用 promise.then 的方式去執(zhí)行回調,將任務在下一個tick執(zhí)行。但是其中 if (isIOS) setTimeout(noop) 這句話是在做什么呢?在iOS >= 9.3.3的UIWebView中,定義的回調函數通過 Promise 的方式推到微任務隊列后,隊列不刷新,需要靠 setTimeout 來強制更新一下,noop 就是一個空函數。
再看第二塊:
else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
}
如果不能用 Promise 就降級使用 MutationObserver。創(chuàng)建了一個文本節(jié)點,并通過 observer 去觀察文本節(jié)點的變化。 characterData: true 這個配置就是當文字變化的時候就會執(zhí)行回調。(counter + 1) % 2 會使文本節(jié)點的文字在 0 、 1 、 0 、 1之間不同變化,這樣就會被 observer 觀察到。MutationObserver 也是微任務。
然后是第三塊:
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
}
當微任務都不被支持時,就要使用宏任務了。其實大多數情況下都不會走到這里,因為 setImmediate 并沒有成為正式的標準,并且兼容性很差。
最后是第四塊:
else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
最后在所有方案都行不通時,只能采用 setTimeout 的方式。之所以有第三塊是因為雖然都是宏任務,但是 setImmediate 會比 setTimeout 快,所以MDN上才會說 setTimeout(fn, 0) 不能成為 setImmediate 的polyfill。就像作者在注釋中寫的那樣:它仍然是比 setTimeout 更好的選擇。
一步一步分析了 $nextTick 源碼后,你是否對它的用法理解更加透徹了呢?
以上就是Vue中$nextTick實現源碼解析的詳細內容,更多關于Vue $nextTick解析的資料請關注腳本之家其它相關文章!

