版本:vue 2.6.11
// noop 空函数 用作函数占位符
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// isUsingMicroTask-浏览器兼容(火狐<=53无法触发微任务,暴露出去的变量,供/platforms/web/runtime/modules/events.js中使用)
export let isUsingMicroTask = false
// 存储所有需要执行的函数,即调用nextTick的参数
const callbacks = []
// 异步锁: 接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。从而保证在同一个事件循环中多次执行nextTick,不会开启多个异步任务,只有一个异步任务
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]()
}
}
// timerFunc 能力检测 根据不同的浏览器环境使用不同的实现方式
// 优先使用微任务 Promise MutationObserver,其次使用宏任务 setImmediate、MessageChannel、setTimeout
// Node定时器四种:
// setTimeout() setInterval() - js语言标准
// setImmediate() process.nextTick() - node特有的
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// IOS 的UIWebView, Promise.then 回调被推入 microTask 队列,但是队列可能不会如期执行
// 因此,添加一个空计时器强制执行 microTask
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。
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)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 因为涉及到this指向,所以不是直接push cb
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// 锁上异步锁,表示异步任务执行函数已经加入到任务队列中,等待同步代码执行完毕后执行异步函数
if (!pending) {
pending = true
// 根据能力检测的结果以不同的方式执行回调队列
timerFunc()
}
// 没有回调函数时返回promise,允许this.nextTick().then()的写法
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
Q&A
- 为什么
nextTick
中setImmediate
优先级比setTimeout
高?明明js运行时setTimeout早于setImmediatesetTimeout
执行的最小时间间隔是约4ms的样子,略微有点延迟
nextTick
宏任务微任务的降级策略?- 微任务:
promise
(最佳策略,兼容性不好),Mutation Observer
- 宏任务:
setImmediate
(只有IE和nodejs支持),MessageChannel
(消息管道,创建一个新的信道并通过信道的两个MessagePort属性(port1,port2)来传递数据postMessage发送数据,onmessage监听
是新API,有兼容性问题),setTimeout
(执行延迟,但兼容性最好)
- 微任务:
flushCallbacks
中为什么要复制并清空原callback
?- 因为
callbacks
相当于是全局的变量 只要遇到nextTick
就把回调函数放到callback
数组中 先备份后清空保证嵌套的nextTick
中的回调函数不在同一次事件循环中执行.避免nextTick
嵌套nextTick
时 如果flushCallbacks
不做特殊处理,直接循环执行回调函数,会导致里面nextTick
中的回调函数会进入回调队列
- 因为