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)容许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是做为现有的类的一个包装。
这种模式建立了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。