从今天开始每天阅读一些源码吧
next-tick官方文档
简介
next-tick的位置在src/core/utils/next-tick.js
next-tick文件
- 最核心的几个方法是flushCallback,nextTick及macroTask与microTask,其中前两者是负责消息的处理与建立,后两者是构建不同的消息执行体,此外还有一个withMacroTask
flushCallback
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]()
}
}
就是一个队列的消费者
nextTick
nextTick的作用是将传入的callback分发到不同的TimerFunc中
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 (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
闭包,当没有cb传入的时候,返回的就是一个简单的promise(microtask),并且在回调队列中会直接触发then事件;否则就是执行回调,并不会有返回值。
function nextTick (cb?: Function, ctx?: Object) {
...
if (!pending) {
pending = true
if (useMacroTask) { // 手动指定使用macroTask方式
macroTimerFunc()
} else {
microTimerFunc()
}
}
...
}
分发部分,这部分是next-tick函数的核心
触发的timerFunc分为macroTask和microTask
pending的状态只有在flushCallback中才会修改,因此分发部分可以保证一次nextTick处理流程中只触发一次timerFunc,亦即只有一次flushCallback事件
由于整个模块中只有一个callback,所以一次nextTick或者是由macro或者是由micro来处理;而若这次nextTick中有macro事件,那么在下一个eventLoop前(或者本次eventloop的最后处理setImmediate前),所有的microTask都无法触发,必须等到microTimerFunc中定义的macroTask来处理
有一个问题,如果先调用microTask然后接着在其还没有被执行的时候再nextTick一个macroTask(在vue中是绑定的v-dom事件),那么macroTask会在下次执行microTask的时候被执行,这明显是不对的
好吧,这个问题和eventloop的触发有关,Vue在所有DOM元素的addEventListener上定义了使用macroTask,所以如果是用户触发,最外层的回掉事件就会首先采用macroTask;而如果使用的是programmatical trigger,则在当前执行栈jsstack内就会触发bubble和capture,不会存在Vue注释里面说的那种问题
参考1
参考2
根据这个特性,写了一个库,用来区分用户触发事件和程序触发事件
timerFunc
macroTask
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
// 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.
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 {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
可以看到,分别使用了setImmediate
,MessageChannel
和setTimeout
三种方法。保证flushCallback会在下一次eventloop中执行
早期的是使用mutationObserve
实现的
microTask
// Determine microtask defer implementation.
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
}
采用promise实现,有意思的一点是针对ios的UIWebViews进行了优化,强行触发了一次microtask的事件处理
withMacroTask
function withMacroTask (fn: Function): Function {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true
const res = fn.apply(null, arguments)
useMacroTask = false
return res
})
}
装饰器,返回了一个通过macro处理的函数,看下使用
platforms\web\runtime\modules\event.js
function add (
event: string,
handler: Function,
once: boolean,
capture: boolean,
passive: boolean
) {
handler = withMacroTask(handler)
if (once) handler = createOnceHandler(handler, event, capture)
target.addEventListener(
event,
handler,
supportsPassive
? { capture, passive }
: capture
)
}
增加事件监听方法,将DOM事件放到了下一个macroTask队列中
总结
- next-ticker.js 只是一个底层的封装,将函数放到函数队列中,并通过microTask和macroTask将处理函数推迟处理;
- 感觉next-ticker需要在上层对macroTask的使用进行判断处理,当然也可能是我还不明确eventloop的具体机制和顺序。