前言:
本文需要一定的事件循环相关知识,想了解事件循环的小伙伴可以看
这里。
本文要弄明白下面两件事:
$nextTick
什么时候执行vue
中nextTick
与$nextTick
区别
1.查看源码中的$nextTick
方法
Vue.prototype.$nextTick = function(fn) {
return nextTick(fn, this)
};
可以看到$nextTick
调用的也是nextTick
方法,只不过$nextTick
默认绑定了this
上下文,也就是Vue
实例对象
2.下面查看nextTick方法
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;
})
}
}
callbacks
一个异步队列,传入的回调函数会被存储在这个数组内,等待时机执行if (!cb && typeof Promise !== 'undefined')
如果没有回调方法,并且当前环境支持Promise
,那么nextTick
返回的是一个Promise
对象
3.查看timerFunc方法
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function() {
p.then(flushCallbacks);
//ios的UIWebViews中,回调推送到微任务队列后不会立即刷新,通过添加空定时器来强制刷新微任务队列
if (isIOS) {
setTimeout(noop);
}
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//如果支持MutationObserver
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)) {
// but it is still a better choice than setTimeout.
timerFunc = function() {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = function() {
setTimeout(flushCallbacks, 0);
};
}
经过一系列的判断方法,用来判断当前执行环境到底支持哪种方法,可以看到最后timerFunc
执行的都是flushCallbacks
方法。
4.flushCallbacks方法
function flushCallbacks() {
pending = false;
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
copies[i]();
}
}
这个方法执行时,会将回调队列做一个浅拷贝,并且初始化这个队列,防止影响下次事件循环,接下来将浅拷贝后的数组进行循环并执行。
到这步,$nextTick方法就算执行完毕了。
总结:
nextTick
与$nextTick
方法基本一致,$nextTick
方法默认绑定vue
实例为上下文。nextTick
上下文需要传入,若不传入则默认绑定为window
。
$nextTick
方法执行时会判断当前执行环境是否支持Promise
若支持则放入Promise.then()
内,若不支持则判断是否支持MutationObserver
,如果不支持的话则会判断是否支持setImmediate
方法,否则的话会加入setTimeout
中。
一次事件循环(event loop
)的过程
宏任务 => 所有微任务 => ui渲染
其中 Promise.then
以及MutationObserver
为微任务,在当前事件循环执行。
setImmediate
、setTimeout
为宏任务,在下次事件循环执行。
下面代码为vue
源码中的nextTick
相关代码,我做了部分注释。
var isUsingMicroTask = false; //是否使用MutationObserver来触发回调函数执行,另作他用,可以在源码中搜索用到的地方,本文不做深究
var callbacks = []; //存储回调函数
var pending = false; //此次nextTick是否执行中的标记
var timerFunc; //触发方法
function noop(a, b, c) {} //空函数,ios用来强制刷新微任务队列
//执行回调队列
function flushCallbacks() {
pending = false; //执行中标记置否
var copies = callbacks.slice(0); //浅拷贝回调
callbacks.length = 0; // 清空数组
for (var i = 0; i < copies.length; i++) {
copies[i](); //执行存储的函数
}
}
//判断当前环境是否支持Promise
//Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,
//如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function() {
p.then(flushCallbacks); //将flushCallbacks放入微任务
//ios的UIWebViews中,回调推送到微任务队列后不会立即刷新,通过添加空定时器来强制刷新微任务队列
if (isIOS) {
setTimeout(noop);
}
};
isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//如果支持MutationObserver
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)) {
// 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); //更改this为传入的ctx,并执行,$nextTick为vue。nextTick为传入的上下文,如果没传则this为window
} 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.prototype.$nextTick = function(fn) {
return nextTick(fn, this)
};
如果想获取更多内容,可以扫描下方二维码,一起学习,一起进步。