nextTick
场景
在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中,因为在created()
钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted()
钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
vm.someData = 'new value'
,该组件不会立即重新渲染。组件会在事件循环队列清空的下一个“tick”更新。Vue.nextTick(callback)
。这样回调函数在 DOM 更新完成后就会调用。Vue.nextTick
用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise
对象。源码
/** * Defer a task to execute it asynchronously. */ export const nextTick = (function () { const callbacks = []; // 需要执行的回调函数 let pending = false; // 是否正在执行回调函数 let timerFunc // 用来触发执行回调函数 function nextTickHandler () { pending = false ; const copies = callbacks.slice(0);
// slice() 方法以新的数组对象,返回数组中被选中的元素。
// slice() 方法选择从给定的 start 参数开始的元素,并在给定的 end 参数处结束
callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // the nextTick behavior leverages the microtask queue, which can be accessed via
// nextTick 行为 利用 微任务队列,他通过
// either native Promise.then or MutationObserver.
// 或者原生Promise.then 或 MutationObserver
// MutationObserver has wider support, however it is seriously bugged in
// MutationObserver 有广泛的支持,尽管它确实是存在严重缺陷,
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers.
// UIWebView 在 IOS大于9.3.3的版本下,当触摸触发事件时,
// It completely stops working after triggering a few times... so, if native
// 它会彻底的停止工作几秒,所以,如果原生
// Promise is available, we will use it:
// Promise是可用的,我们将使用它
/* istanbul ignore if */
if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) }
// 用来触发执行回调函数
timerFunc = () => { p.then(nextTickHandler).catch(logError) // in problematic UIWebViews, Promise.then doesn't completely break, but
// 在有问题的UIWebViews,Promise.then不会完全中断,但
// 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) }
} 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,
// 使用 MutationObserver 当 Promise 不可用
// e.g. PhantomJS, iOS7, Android 4.4
// 例如, PhantomJS, ios7,Android 4.4
var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true })
timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (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() }
、 if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })()
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
nextTickHandler 这个函数用来执行callback里存储的所有回调函数,接下来是触发方式赋值给timerFunc
1 、先判断是否原生支持promise,如果支持,则利用promise来触发执行回调函数;
2 、否则,如果支持MutationObserver,则实例化一个观察者对象,观察文本节点发生变化时,触发执行所有回调函数。
3 、如果都不支持,则利用setTimeout设置延时为0。
4、最后是queueNextTick
函数。因为nextTick
是一个即时函数,所以queueNextTick
函数是返回的函数,接受用户传入的参数,用来往callbacks里存入回调函数。
从上面的介绍,可以得知timeFunc()
一共有三种实现方式。
Promise
MutationObserver
setTimeout
其中Promise
和setTimeout
很好理解,是一个异步任务,会在同步任务以及更新DOM的异步任务之后回调具体函数。
MutationObserver
是HTML5中的新API,是个用来监视DOM变动的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。
调用过程很简单,但是有点不太寻常:你需要先给他绑回调:
var mo = new MutationObserver(callback)
通过给MutationObserver
的构造函数传入一个回调,能得到一个MutationObserver
实例,这个回调就会在MutationObserver
实例监听到变动时触发。
MutationObserver
实例绑定好了回调,他具体监听哪个DOM、监听节点删除还是监听属性修改,还没有设置。而调用他的observer
方法就可以完成这一步:var domTarget = 你想要监听的dom节点 mo.observe(domTarget, { characterData: true //说明监听文本内容的修改。 })
nextTick
中 MutationObserver
的作用就如上图所示。在监听到DOM更新后,调用回调函数。
其实使用 MutationObserver
的原因就是 nextTick
想要一个异步API,用来在当前的同步代码执行完毕后,执行我想执行的异步回调,包括Promise
和 setTimeout
都是基于这个原因。