jsvascript 事件環機制是 nextTick
的核心原理,不理解強烈建議仔細閱讀下面文章在看下面的代碼。javascript
接下這一篇後面還會更新 Vue3
的 nextTick
實現java
一文搞懂EventLoop與堆棧,宿主環境之間的事件環機制ios
在瀏覽器環境中設計模式
常見的
macrotask
有瀏覽器
MessageChannel
setTimeout
postMessage
setImmediate
常見的
microtask
有緩存
MutationObsever
Promise.then
在 Vue2.5
版本以上,單獨維護了一個 nextTick
實現的文件 src/core/util/next-tick.js
。 src 目錄下存放着 Vue 的源代碼實現,下面是 src 目錄下的核心代碼的目錄組織。markdown
compiler
:編譯器代碼,生成render函數core
:核心代碼,與平臺無關的核心部分platforms
:平臺支持,分平臺特有代碼,相關入口文件serve
:服務端渲染sfc
:單文件系統實現shard
:共享代碼,整個代碼庫通用代碼接下來看看 nextTick
的源碼閉包
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIOS, isNative } from './env'
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// Here we have async deferring wrappers using both microtasks and (macro) tasks.
// In < 2.4 we used microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using (macro) tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use microtask by default, but expose a way to force (macro) task when
// needed (e.g. in event handlers attached by v-on).
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
// Determine (macro) task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// Determine microtask defer implementation.
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else {
// fallback to macro
microTimerFunc = macroTimerFunc
}
/** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a (macro) task instead of a microtask. */
export function withMacroTask (fn: Function): Function {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true
const res = fn.apply(null, arguments)
useMacroTask = false
return res
})
}
export function nextTick (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
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
複製代碼
在這裏, Vue 同時聲明瞭app
microTimerFunc
(微任務實現)macroTimerFunc
(宏任務實現)useMacroTask
(是否使用宏任務標誌位)從上往下,咱們先看到的是對宏任務實現的代碼邏輯async
setImmediate
能夠使用MessageChannel
能夠使用setTimeout
能看到的是 這裏宏任務實現採用方式的優先級,和 Vue
會判斷是不是這些實現方式的方法是否爲原生實現。避免第三方 profill
的不一致的行爲。
接下來是 microTimerFunc
微任務實現,能夠看到 Vue
會判斷是否存在原生的 Promise
實現,存在則直接使用 Promise.resolve
處理,不存在則指向宏任務的實現了。
這裏 Vue 導出了兩個函數
nextTick
withMacroTask
這裏使用閉包的方法,緩存了在同一個 tick
中屢次執行 nextTick
傳入的回調函數,將這些回調函數壓成同一個同步任務在下一個 tick
中一次性執行,保證了屢次執行的準確性。 同時根據狀態,標識位使用對應的實現方式。
導出的 withMacroTask
其實是一個裝飾函數,它將傳入的函數包裝成一個新的函數,返回值仍是原來的函數執行的結果,在函數執行時將 useMacroTask 置爲 true ,使執行時的 nextTick 將強制走 宏任務實現。
裝飾器模式 裝飾器模式(Decorator Pattern)容許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。
這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。