这一篇主要讲讲
nextTick
源码,看看该方法的实现,以及为什么能在这个方法里保证拿到DOM
节点。vue
nextTick
方法在./src/core/util/next-tick.js
,下面为部分源码展现:promise
nextTick
方法接受两个入参,分别是回调方法cb
和上下文ctx
;cb
参数都会往队列推入一个函数,后续任务队列根据cb
参数判断是否调用cb
或者是否执行_resolve(ctx)
修改promise
状态;pending
状态是否执行任务promise
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
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
复制代码
先来讲说调用nextTick
的返回值,由于返回值是一个promise
,因此咱们能够使用then
的写法或者async/await
的写法,加上使用cb
的写法,存在三种写法。bash
this.$nextTick(function() {
// do something
})
or
this.$nextTick().then((ctx)=> {
// do something
})
or
await this.$nextTick()
// do something
复制代码
接下来则是nextTick
里比较重要的方法timerFunc
的实现:框架
Promise
;MutationObserver
;setImmediate
;setTimeout
;if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
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)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
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)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
复制代码
从代码中isUsingMicroTask
中能够看到只有Promise
、MutationObserver
属于微任务,另外两个则属于宏任务;看到该方法的实现咱们就能够知道为何在nextTick
方法中能保证拿到DOM
。async
两种场景的解释:函数
vue
第一次初始化的时候,咱们在beforeCreated
和created
生命周期里想要使用DOM
则必须使用nextTick
,这是由于初始化的过程属于宏任务,整个函数调用栈未清空,nextTick
的回调属于微任务,因此nextTick
的回调必须在整个初始化结束后才会执行。data
数据后,又如何保证获取修改后的数据DOM
?修改data
数据其实是触发组件实例的watcher
执行update
更新,而在update
里面又执行了queueWatcher
,下面👇则是queueWatcher
方法的代码,在代码里面咱们能够看到最后实际上也是调用nextTick(flushSchedulerQueue)
。所以,想获取data
修改后的DOM
,调用nextTick
能保证这种任务执行的顺序。了解watcher
能够看这篇juejin.im/post/5d181b…。oop
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
复制代码
其实queueWatcher
方法里面的逻辑还告诉了咱们另一个框架知识点:post
为何咱们同时修改多个data属性,不会屡次更新视图?ui
在update
方法里,由于最后实际上调用nextTick
执行微任务去更新视图,了解过event loop
机制的应该知道,必须等待当前宏任务的调用栈清空才去执行微任务,这也就是为何当咱们同时修改多个data
属性时候,该判断if (has[id] == null)
防止重复添加更新任务,而且利用了event loop
机制在合适的时机去更新视图。this