在咱们开发项目的时候,总会碰到一些场景:当咱们使用vue操做更新dom后,须要对新的dom作一些操做时,可是这个时候,咱们每每会获取不到跟新后的DOM.由于这个时候,dom尚未从新渲染,因此咱们就要使用vm.$nextTick方法。前端
nextTick接受一个回调函数做为参数,它的做用将回调延迟到下次DOM跟新周期以后执行。vue
methods:{
example:function(){
//修改数据
this.message='changed'
//此时dom尚未跟新,不能获取新的数据
this.$nextTick(function(){
//dom如今跟新了
//能够获取新的dom数据,执行操做
this.doSomeThing()
})
}
}
复制代码
在用法中,咱们发现,什么是下次DOM更新周期以后执行,具体是何时,因此,咱们要明白什么是DOM更新周期。 在Vue当中,当视图状态发生变化时,watcher会获得通知,而后触发虚拟DOM的渲染流程,渲染这个操做不是同步的,是异步。Vue中有一个队列,每当渲染时,会将watcher推送这个队列,在下一次事件循环中,让watcher触发渲染流程。算法
简单来讲,就是提高性能,提高效率。 咱们知道Vue2.0使用虚拟dom来进行渲染,变化侦测的通知只发送到组件上,组件上的任意一个变化都会通知到一个watcher上,而后虚拟DOM会对整个组件进行比对(diff算法,之后有时间我会详细研究一下),而后更新DOM.若是在同一轮事件循环中有两个数据发生变化了,那么组件的watcher会收到两次通知,从而进行两次渲染(同步跟新也是两次渲染),事实上咱们并不须要渲染这么屡次,只须要等全部状态都修改完毕后,一次性将整个组件的DOM渲染到最新便可。缓存
其实很简单,就是将收到的watcher实例加入队列里缓存起来,而且再添加队列以前检查这个队列是否已存在相同watcher。不存在时,才将watcher实例添加到队列中。而后再下一次事件循环中,Vue会让这个队列中的watcher触发渲染并清空队列。这样就保证一次事件循环组件屡次状态改变只须要一次渲染更新。bash
咱们知道js是一门单线程非阻塞的脚本语言,意思是执行js代码时,只有一个主线程来处理全部任务。非阻塞是指当代码须要处理异步任务时,主线程会挂起(pending),当异步任务处理完毕,主线程根据必定的规则去执行回调。事实上,当任务执行完毕,js会将这个事件加入一个队列(事件队列)。被放入队列中的事件不会马上执行其回调,而是当前执行栈中全部任务执行完毕后,主线程会去查找事件队列中是否有任务。
异步任务有两种类型,微任务和宏任务。不一样类型的任务会被分配到不一样的任务队列中。
执行栈中全部任务执行完毕后,主线程会去查找事件队列中是否有任务,若是存在,依次执行全部队列中的回调,只到为空。而后再去宏任务队列中取出一个事件,把对应的回调加入当前执行栈,当前执行栈中全部任务都执行完毕,检查微任务队列是否有事件。无线循环此过程,叫作事件循环。dom
在咱们使用vm.$nextTick中获取跟新后DOM时,必定要在更改数据的后面使用nextTick注册回调。异步
methods:{
example:function(){
//修改数据
this.message='changed'
//此时dom尚未跟新,不能获取新的数据
this.$nextTick(function(){
//dom如今跟新了
//能够获取新的dom数据,执行操做
this.doSomeThing()
})
}
}
复制代码
若是是先使用nextTick注册回调,而后修改数据,在微任务队列中先执行使用nextTick注册的回调,而后才执行跟新DOM的回调,因此回调中得不到新的DOM,由于尚未更新。ide
methods:{
example:function(){
//此时dom尚未跟新,不能获取新的数据
this.$nextTick(function(){
//dom没有跟新,不能获取新的dom
this.doSomeThing()
})
//修改数据
this.message='changed'
}
}
复制代码
methods:{
example:function(){
//先试用setTimeout向宏任务中注册回调
setTimeout(()=>{
//如今DOM已经跟新了,能够获取最新DOM
})
//而后修改数据
this.message='changed'
}
}
复制代码
setTimeout属于宏任务,使用它注册回调会加入宏任务中,宏任务执行要比微任务晚,因此即使是先注册,也是先跟新DOM后执行setTineout中设置回调。函数
因为nextTick会将回调添加到任务队列中延迟执行,因此在回调执行以前,若是反复使用nextTick,Vue并不会将回调添加到任务队列中,只会添加一个任务。Vue内部有一个列表来存储nextTick参数中提供的回调,当任务触发时,以此执行列表里的全部回调并清空列表,其代码以下(简易版):oop
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]()
}
}
let microTimeFun
const p=Promise.resolve()
microTimeFun=()=>{
p.then(flushCallBacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending=true
microTimeFun()
}
}
复制代码
理解相关变量:
this.$nextTick().then(function(){
//dom跟新了
})
复制代码
要实现这个功能,只须要在nextTIck中判断,若是没有提供回调且当前支持Promise,那么返回Promise,而且在callbacks中添加一个函数,当这个函数执行时,执行Promise的resolve,便可,代码以下
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) {
cb.call(ctx);
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
}
复制代码
到此,nextTick原理基本上已经讲完了。那咱们如今能够看看真正vue中关于nextTick中的源码,大概咱们都能理解的过来了,源码以下。
var 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)) {
var p = Promise.resolve();
timerFunc = function () {
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)
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
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 = function () {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function () {
setTimeout(flushCallbacks, 0);
};
}
function nextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
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(function (resolve) {
_resolve = resolve;
})
}
}
复制代码
这篇文章大概花了两天时间才写出来的,充分的参考了<深刻浅出vue.js>这本书,充分了理解书上关于vm.$nextTick中的每一句话,同时也对js中的事件循环有了进一步认识,对js运行机制也进一步加深。做为前端小白,不想只局限于调用各类API,更要知道其原理,天天进步一小步。但愿你们能多多与我讨论交流。