vue中的nextTick源码解析
nextTick 在 vue2 中
文档介绍
-
参数:
{Function} [callback]
{Object} [context]
-
用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了 }) // 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示) Vue.nextTick() .then(function () { // DOM 更新了 })
2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不原生支持 Promise (IE:你们都看我干嘛),你得自己提供 polyfill。
源码解析
// 执行队列中的函数
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// promise为第一优先级,如果支持promise,则直接使用promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (
!isIE &&
// 如果支持MutationObserver,使用MutationObserver监听
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
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)) {
// 第三优先级使用setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最后使用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e: any) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// 这里处理第二种调用方式
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
分块解析
promise
promise经常用来处理异步调用,属于微任务。在vue2中最优先选择的就是promise,在Promise.resolve().then()中执行的任务将进入微任务队列。
MutationObserver
MutationObserver可能用的比较少,它提供了监视对 DOM 树所做更改的能力。
在promise不可用时,使用了MutationObserver对新建的DOM进行监听,并通过修改DOM来触发事件进入微任务队列
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)
}
setImmediate
setImmediate只在node中使用,属于宏任务。在事件循环机制中,他在check阶段进行执行。在一次正常的同层级的事件循环中,比setTimeout要快。
setTimeout
js中的老常客,延迟执行函数,属于宏任务,不设时间或者把时间设为0时就是为了将任务置入事件循环中,等下一个循环再执行。
nextTick 在 vue3 中
文档介绍
nextTick()
等待下一次 DOM 更新刷新的工具方法。
-
类型
function nextTick(callback?: () => void): Promise<void>
-
详细信息
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。 -
用法
async increment() {
this.count++ // DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick() // DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
源码
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
let currentFlushPromise: Promise<void> | null = null
export function nextTick<T = void>(
this: T,
fn?: (this: T) => void
): Promise<void> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
解析
在vue3中,nextTick就简单很多,只能通过promise创建微任务。这是因为放弃支持IE。在官方文档就写到一下部分
转载自:https://juejin.cn/post/7239593701806440503