Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM
,但有时候业务需要必须对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)
这个 api 了。
nextTick
回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()
的判断,因为这里还存在兼容性优雅降级的问题。可见 Vue 开发团队的深思熟虑,对性能的良苦用心。
工程源码:
src/core/util/next-tick.js
// 储存 nextTick 的回调函数, 同一时期可能有多个地方会调用 $nextTick()
const callbacks = []
// 节流阀: 目的是同一时刻只开启一个 timerFunc
let pending = false
// 定时函数
let timerFunc
// 刷新队列的函数
function flushCallbacks () {
pending = false
// 复制 callback
const copies = callbacks.slice(0)
// 清除 callback
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
// 以此调用 nextTick 回调
copies[i]()
}
}
// $nextTick 会依次判断是否支持 Promise => MutationObserver => setImmediate => setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
// 如果支持 Promise, 就使用 Promise 实现
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
} else if (!isIE && typeof MutationObserver !== 'undefined') {
// 如果 Promise 不支持, 但支持 MutationObserver
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
// 改变 counter 属性触发 flushCallbacks
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 上面两种都不支持, 用 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 都不支持使用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb, ctx) {
// 存入 nextTick 回调, 可存入多个
callbacks.push(() => {
cb && cb.call(ctx)
})
if (!pending) {
pending = true
timerFunc()
}
}
$nextTick
的降级策略Promise
> MutationObserver
> setImmediate
> setTimeout