this.$nextTick
概念:
vue的响应式并不是只有数据变化之后,dom就会立即发生变化,而是按照一定策略更新dom的。
即放到nextTick中的操作不会立即执行,而是等数据更新,dom更新完成之后执行。在准确的说就是,将nextTick的方法延迟到下次dom更新循环之后执行。
分析:
2.2.5中vue使用mutationObserver实现。
2.5.0就做降级处理,降级的方案依次是:setImmediate,MessageChannel, setTimeout , 前两个都有兼容性问题,最后兜底的就是setTimeout了。
vue.nextTick 方法的实现原理:
1、 vue异步队列的方式来控制dom更新和nextTick回调先后执行
2、微观队列因为其高优先级特性,能确保队列中微任务在一次事件循环前被执行
3、因为兼容性问题,vue不得不做了微观事件到宏观事件的降级
总结:
因为vue的异步更新策略,导致我们数据的修改不会立即体现到dom变化上。
mutationObserver 是html5新增的属性,用于监听dom修改事件,能够监听节点属性,文本内容,子节点等改动。
nextTick的源码 兼容判断,先判断是否支持promise (es6特性,兼容性) ---mutationObserver(ios有bug 选择使用settimeout) ------ setImmediate (兼容ie和node) ------ setTimeout
nextTick总是 由于 setTimeout 执行的。
源码文件路径:vue/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]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { //---存在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) //ios环境 } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( //不是ie 且存在mutationObserver 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. // Technically 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) } } 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 }) } }