在vue的体系中,数据的双向绑定主要通过<code>Object.defineProperty</code>进行数据从模型到视图的流动及通过DOM事件进行数据从视图到模型的流动。这些都比较容易理解,但是如果频繁的更改模型的话,就会造成频繁的更新View,会造成频繁的dom操作,这是比较耗时的,因此需要进行优化,Vue2.0是通过批处理进行优化的,而异步执行队列,是批处理中一个很重要的一个环节。
在vue.js(Vue.js v2.1.10)416-490行,描述了这一神奇的异步执行队列。下面我们将进行相关的代码分析。
- 1.函数整体
var nextTick =(function(){
return function queueNextTick (cb, ctx) {}
})()
我们可以看到整体来说,这是一个由立即执行函数构成的,返回值是函数的一个函数,我们可以使用下面的测试代码
var nextTick =(function(){
return function queueNextTick (cb, ctx) {
console.log(cb);
}
})();
nextTick("this is test1")
- 2.函数内容
var nextTick = (function () {
var callbacks = [];
var pending = false;
var timerFunc;
function nextTickHandler () {
}
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
var logError = function (err) { console.error(err); };
timerFunc = function () {
p.then(nextTickHandler).catch(logError);
if (isIOS) { setTimeout(noop); }
};
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
var counter = 1;
var observer = new MutationObserver(nextTickHandler);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
}
return function queueNextTick (cb, ctx) {
}
})();
我们看到在函数内部,有三个函数,nextTickHandler,timerFunc,以及queueNextTick 。
其中
if(typeof Promise !== 'undefined' && isNative(Promise)){
}
else if(typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'){
}else{
}
是用来判断当前执行环境,给timerFunc 选择一个合适的执行条件。如果是支持Promise的环境中,timerFunc 中则用<code>p.then(nextTickHandler)</code>和 <code>setTimeout(nextTickHandler, 0);</code>执行回调;如果是MutationObserver环境则通过修改文本节点的值的变化触发调用的的。关于为什么优先使用Promise,知乎里也在讨论这件事,点这里看详情。
- 3.函数的返回函数
function queueNextTick (cb, ctx) {
var _resolve;
callbacks.push(function () {
if (cb) { cb.call(ctx); }
if (_resolve) {
console.log(0);
_resolve(ctx); }
});
if (!pending) {
pending = true;
timerFunc();
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
console.log("now")
_resolve = resolve;
})
}
}
当我们使用<code>nextTick(function Test(){console.log("1" +this.isIOS);},this)</code>时,我们可以发现 Test封装后被放进了回调队列callbacks中,
if (cb) { cb.call(ctx); }
if (_resolve) {
console.log(0);
_resolve(ctx); }
注意这里的_resolve,他是在
if (!cb && typeof Promise !== 'undefined') {
return new Promise(function (resolve) {
_resolve = resolve;
})
}
里面进行了赋值,因为promise注册函数是立即执行的,因此在返回前给当前的域中的_resolve赋了值。当进行回调时,便可以使用_resolve的值。我们可以使用执行空函数进行测试:
console.log(nextTick();)//打印是Promise的相关内容
- 4.nextTickHandler队列回调控制函数
function nextTickHandler () {
pending = false;
//对callbacks进行深复制
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++) {
if(i==0){
copies[i]();
}
copies[i]();
}
}
此函数便是在触发时把回调队列中函数依次调用,特别注意 pending=flase这句话用来把执行回调过程中新加进回调数组的计入下一个tick,另外需要注意<code> var copies =callbacks.slice(0); callbacks.length = 0;
</code>这句话,用的很好,首先对回掉数组进行深复制,然后把callbacks置空。之所以不用callbacks=[],是因为设置length=0可以不改变原有对象的地址。
- 5.如何实现队列的功能
我们使用测试代码:
nextTick().then(()=>{
console.log("last");
})
var ss={
isIOS:"sss"
}
nextTick(function(){
console.log("1" +this.isIOS);
},ss)
最后我画出了队列图