前提概要
- 1、浏览器的执行
浏览器中,一个页面的js的执行依赖于一个主线程,但是用户点击的触发,ajax数据请求,io读取等依赖于其他相应的模块。当主线程的执行堆栈执行完之后,会去读取任务队列,将任务(回调)放入执行堆栈执行,依次循环。任务队列里的任务由各种执行场景触发产生。
- 2、任务队列的任务分为宏任务(macroTask),微任务(microTask)。
宏任务:整段的script, setTimeout, setInterval, setImmediate(IE10), 用户交互操作, io口
微任务:promise, process.nextTick(nodejs)
执行顺序:一整段script相当于宏任务,如果里面有其它setTimeout等宏任务触发,便会在其异步场景完成后(如定时完成)推入到宏任务队列。有promise等微任务,则会将其推入到微任务队列,当前的宏任务执行完之后,会去读取微任务队列执行完,接下来进行读取宏任务队列。从这种顺序看来,微任务往往执行于一次事件循环结束,而宏任务执行于下一次事件循环开始。所以promise的执行往往先于promise。
例: let p = Promise.resolve(); p.then(()=> { console.log(1); }) setTimeout(() => { console.log(2); },0); console.log(3); // 打印顺序:3 1 2
vue中的nextTick
功能:在下次 DOM 更新循环结束之后执行延迟回调 vue中的nextTick是借助于js的宏任务与微任务,实现执行回调的普通函数
vue表明默认使用microTask,特殊下可以使用macroTask
From vue version : "2.5.17-beta.0"
// 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).
nextTick的实现
//1、声明macroTask, macroToask, callbasks, useMacroTask等,
//2、各种环境侦测下建立宏任务与微任务
第一轮侦测:
a、先检查是否存在setImmediate,有则建立宏任务
b、否则检查是否存在MessageChannel,有则建立宏任务
c、否则使用setTimeout建立宏任务
第二轮侦测:
a、检查是否存在promise,有则建立微任务
b、否则微任务等于宏任务.
//3、暴露withMacroTask ,可以往宏任务推入回调
//4、暴露nextTick:可以将回调推入微/宏任务等待执行。
附:vue中nextTick源码
const callbacks = []
let pending = false
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
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)
}
}
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
} else {
microTimerFunc = macroTimerFunc
}
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()
}
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}