众所周知,随着
Vue
技术的愈来愈热,大量的前端开发者开始探究这门神奇的框架,笔者也是从JQuery
时代一脚迈进了Vue
的世界。谈到Vue
,在这呢,就不得不提一下笔者在研究一个Vue
项目的时候碰到的问题,父组件修改标志位变量,而子组件的相应组件并无显示,后来经过多方研究,发现了Vue.nextTick
这个原型方法能够达到我想要的这个效果,因此笔者今天也来谈谈这个神奇的方法。前端
参数node
{Function} [callback]
{Object} [context]
用法es6
在下次 DOM 更新循环结束以后执行延迟回调。在修改数据以后当即使用这个方法,获取更新后的 DOM。web
// 修改数据
vm.msg = 'Hello'
// DOM 尚未更新
Vue.nextTick(function () {
// DOM 更新了
})
// 做为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})
复制代码
这里其实涉及到
js
的事件循环机制,有兴趣的话能够右转 js事件循环数组
具体使用场景各位小伙伴应该也不用笔者多啰嗦了,今天笔者的重点仍是研究一下这个东西源码是怎么实现的,毕竟做为当代前端一员至少不能只会用 API
了,我们仍是去底层伪装研究一下是吧。promise
其实话提及来,咱们就得来了解一下这个
js
是单线程的这个特性上来了,它其实全部事件的处理都依赖于这一个事件循环机制,,主线程的执行过程就是一个 tick,而全部的异步结果都是经过 “任务队列” 来调度被调度,消息队列中存放的是一个个的任务task
。 规范中规定task
分为两大类,分别是macro task
(宏任务) 和micro task
(微任务),而且每一个macro task
结束后,都要清空全部的micro task
。浏览器
回到正题,Vue.nextTick
怎么实现当前页面更新完以后最先执行它所绑定的回调呢,这就用到了咱们上面所说的这个任务队列,每次当前宏任务执行完毕以前,都会清空全部微任务,那么为了在界面更新完以后最短期内执行回调,最佳选择不就是这个微任务了么,利用这个机制,咱们总能在下次事件循环以前把咱们要处理的事件处理掉。bash
常见的宏任务有
setTimeout
、MessageChannel``、postMessage
、setImmediate
闭包
微任务有
MutationObserver
和Promise.then
以及node
的process.nextTick
框架
固然,为了程序的优化和性能提高,咱们的最佳选择固然是 Promise
啦,但是呢,Promise
属于es6
中提出的,部分浏览器可能出现不兼容的状况 (PS: IE:你看我干吗?)
,因此官方就给了一个优雅降级策略,若是当前浏览器支持 Promise
则使用Promise
,其次就是MutationObserver
,若是以上两个都不支持,就只能搬出咱们的setTimeout
了。话很少说,下面开始搬代码。
//存储须要触发的回调函数
var callbacks=[];
/**是否正在等待的标志(false:容许触发在下次事件循环触发callbacks中的回调,
* true: 已经触发过,须要等到下次事件循环)
*/
var pending=false;
//设置在下次事件循环触发callbacks的触发函数
var timerFunc;
复制代码
上面的这个timerFunc
将用于达到触发条件后触发全部回调函数
//处理callbacks的函数
function nextTickHandler() {
// 能够触发timeFunc
pending=false;
//复制callback
var copies=callbacks.slice(0);
//清除callback
callbacks.length=0;
for(var i=0;i<copies.length;i++){
//触发callback的回调函数
copies[i]();
}
}
复制代码
这部分代码就是实现触发全部绑定的回调函数的主要逻辑部分,下面咱们来看看官方的的优雅降级策略怎么实现的
//若是支持promise,使用promise实现
if(typeof Promise !=='undefined' && isNative(promise)){
var p=Promise.resolve();
var logError=function (err) {
console.error(err);
};
timerFunc=function () {
p.then(nextTickHandler).catch(logError);
//iOS的webview下,须要强制刷新队列,执行上面的回调函数
if(isIOS) {setTimeout(noop);}
};
// 若是Promise不支持,但支持MutationObserver
// H5新特性,异步,当dom变更是触发,注意是全部的dom都改变结束后触发
} 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 () {
counter=(counter+1)%2;
textNode.data=String(counter);
};
} else {
//上面两种都不支持,用setTimeout
timerFunc=function () {
setTimeout(nextTickHandler,0);
};
}
复制代码
看完这段代码,你们可能对官方的这个降级策略有了一种恍然大悟的感受,不过可能你们也会有疑问,这个MutationObserver的实现方式怎么这么诡异,那让咱们来看看它的用法吧。
该构造函数用于实例化一个新的 MutaionObserver ,同时指定触发 DOM 变更时的回调函数:
var observer = new MutationObserver(callback);
复制代码
callback,即回调函数接收两个参数,第一个参数是一个包含了全部 MutationRecord 对象的数组,第二个参数则是这个MutationObserver 实例自己。具体详细介绍能够参考 深刻了解MutationObserver。
咳咳咳,回到正题
//nextTick接收的函数,参数1:回调函数 参数2:回调函数的执行上下文
return function queueNextTick(cb,ctx) {
//用于接收触发Promise.then中回调的函数
//向回调函数中pushcallback
var _resolve;
callbacks.push(function () {
//若是有回调函数,执行回调函数
if(cb) {cb.call(ctx);}
//触发Promise的then回调
if(_resolve) {_resolve(ctx);}
});
//是否执行刷新callback队列
if(!pending){
pending=true;
timerFunc();
}
//若是没有传递回调函数,而且当前浏览器支持promise,使用promise实现
if(!cb && typeof Promise !=='undefined'){
return new Promise(function (resolve) {
_resolve=resolve;
})
}
}
复制代码
以上其实就是你调用这个方法实际调用的函数啦,利用闭包原理保存了前面提到的各个函数的引用,首先他会把你传入的回调函数包装一下保存到callback
数组中。
若是当前队列还未执行过回调,那么开始执行回调,并把pending
标志位置为true
,表示当前任务队列已经执行过回调。
而后最后加一层判断,若是当前浏览器具备Promise
环境且未传递回调函数则采用Promise
执行。
最后附上完整代码
export const nextTick=(function () {
//存储须要触发的回调函数
var callbacks=[];
//是否正在等待的标志(false:容许触发在下次事件循环触发callbacks中的回调,
// true: 已经触发过,须要等到下次事件循环)
var pending=false;
//设置在下次事件循环触发callbacks的触发函数
var timerFunc;
//处理callbacks的函数
function nextTickHandler() {
// 能够触发timeFunc
pending=false;
//复制callback
var copies=callbacks.slice(0);
//清除callback
callbacks.length=0;
for(var i=0;i<copies.length;i++){
//触发callback的回调函数
copies[i]();
}
}
//若是支持promise,使用promise实现
if(typeof Promise !=='undefined' && isNative(promise)){
var p=Promise.resolve();
var logError=function (err) {
console.error(err);
};
timerFunc=function () {
p.then(nextTickHandler).catch(logError);
//iOS的webview下,须要强制刷新队列,执行上面的回调函数
if(isIOS) {setTimeout(noop);}
};
// 若是Promise不支持,但支持MutationObserver
// H5新特性,异步,当dom变更是触发,注意是全部的dom都改变结束后触发
} 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 () {
counter=(counter+1)%2;
textNode.data=String(counter);
};
} else {
//上面两种都不支持,用setTimeout
timerFunc=function () {
setTimeout(nextTickHandler,0);
};
}
//nextTick接收的函数,参数1:回调函数 参数2:回调函数的执行上下文
return function queueNextTick(cb,ctx) {
//用于接收触发Promise.then中回调的函数
//向回调函数中pushcallback
var _resolve;
callbacks.push(function () {
//若是有回调函数,执行回调函数
if(cb) {cb.call(ctx);}
//触发Promise的then回调
if(_resolve) {_resolve(ctx);}
});
//是否执行刷新callback队列
if(!pending){
pending=true;
timerFunc();
}
//若是没有传递回调函数,而且当前浏览器支持promise,使用promise实现
if(!cb && typeof Promise !=='undefined'){
return new Promise(function (resolve) {
_resolve=resolve;
})
}
}
})()
复制代码
好啦本文暂时介绍到这里,若是发现笔者写的不对的地方,欢迎给笔者留言。