vue.nextTick()
参数
{Function} [callback]:回调函数,不传时提供promise调用{Object} [context]:回调函数执行的上下文环境,不传默认是自动绑定到调用它的实例上。
作用 (官方解释)
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了 }) // 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示) Vue.nextTick() .then(function () { // DOM 更新了 })
应用场景
在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
原因是在 created() 钩子函数 执行的时候 DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的 js 代码放进 Vue.nextTick() 的回调函数中。
与之对应的就是 mounted 钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
剖析源码
nextTick 的实现单独有一个JS文件来维护它,在src/core/util/next-tick.js中
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
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]()
}
}
//这里我们有使用微任务的异步延迟包装器。
//在2.5中,我们使用(宏)任务(结合微任务)。
//然而,在重新绘制之前更改状态时,它有一些微妙的问题
//(例如#6813,出-入转换)。
//此外,在事件处理程序中使用(宏)任务会导致一些奇怪的行为
//无法规避的风险(例如#7109、#7153、#7546、#7834、#8109)。
//所以我们现在在任何地方都使用微任务。
//这种权衡的一个主要缺点是存在一些场景
//微任务的优先级太高,可能会在两者之间触发
//顺序事件(例如,#4521、#6690,有变通方法)
//甚至在同一事件的冒泡之间(#6566)。
let timerFunc
//nextTick行为利用可以访问的微任务队列
//通过本地Promise。然后或MutationObserver。
//MutationObserver有更广泛的支持,但它被严重窃听
//iOS中的UIWebView>=9.3.3(在触摸事件处理程序中触发时)。它
//触发几次后完全停止工作…因此,如果是本机
//承诺可用,我们将使用它:
//*伊斯坦布尔忽略下一行,$flow禁用行*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
//在有问题的UIWebViews中,Promise。然后不会完全断裂,但是
//它可能会陷入一种奇怪的状态,回调被推送到
//微任务队列,但队列不会被刷新,直到浏览器
//需要做一些其他工作,例如处理计时器。因此,我们可以
//通过添加空计时器“强制”刷新微任务队列。
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//如果本地Promise不可用,请使用MutationObserver,
//例如PhantomJS、iOS7、Android 4.4
//(#6466 MutationObserver在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)) {
// IOS 的UIWebView, Promise.then 回调被推入 microTask 队列,但是队列可能不会如期执行
// 因此,添加一个空计时器强制执行 microTask
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
//回退到setTimeout。
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 抛出
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
})
}
}