写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】NextTick - 源码版 之 独立自身
好的,今天到了 nextTick 的环节,之前我看的版本是 2.5.17,然后瞄了一眼 2.6 的,发现对于 nextTick 修改了 少部分内容,但是不太大,所以就一起记录下来
(如果改太多,就懒得看了.....反正了解一个思想以及实现思路就行了)
nextTick 是一个在 Vue 中比较独立的东西,可以直接拿出来为你的项目服务
nextTick 涉及的点,就下面这些
1、任务队列callbacks
2、任务队列执行函数 flushCallbacks
3、控制(宏任务,微任务)注册标志位 pending
4、宏任务,微任务
没看懂?没关系,后面会慢慢说
这篇先讲 nextTick 自身,下篇再讲 nextTick 和 Vue 的关联
接下来就是一个个去详细记录了
宏任务,微任务
这个知识点,很重要,也不算太简单,在网上也能找到很多很好的讲解,比如下面这篇文章,在这里不会特别解释这两个,毕竟主题不是这个
https://juejin.im/post/59e85eebf265da430d571f89
宏微任务的下面总结也是个人理解,有错尽管骂我
那么这里就先记录一下相关的结论
1、宏任务和微任务都是异步
2、宏任务和微任务会被注册到两个不同的队列中
3、宏任务队列不是一次性清空执行,而是执行一个宏任务时,
然后去清空执行一列微任务队列
接着再执行下一个宏任务.....循环往复,直到所有队列都为空
什么是一个宏任务
比如 一个 setTimeout 就是一个宏任务,两个 setTimeout 就是两个宏任务
例子说明执行顺序
比如现在,宏任务队列中有两个 setTimeout,微任务队列中有两个 Promise
假设现在正在执行第一个宏任务 setTimeout,执行完之后,会开始清空执行 微任务队列
于是开始执行了两个Promise
结束之后,接着执行 另一个宏任务, setTimeout
以前我以为是 宏任务队列执行完,再执行微任务队列,发现不是,很受伤,都是了解 nextTick 源码让我有机会重新了解了一遍 这个知识点
常见宏任务
setTimeout
setInterval
setImmediate
script
MessageChannel
常见微任务
Promise
MutationObserver
Object.observe(废弃)
process.nextTick(node)
Vue 中的宏任务 和 微任务 源码
以下谈的是 版本 2.5.17 的,在 2.6 中,去掉宏任务了
在这里先埋下两个问题
1、Vue为什么需要宏任务和 微任务
2、Vue在哪里使用到了宏任务和微任务
这两个问题会记录在另外一篇文章
Vue 中有两个函数,macroTimerFunc 用于注册宏任务,microTimerFunc 用于注册微任务
以适用于不同的场景,下面就是这两个函数的源码
1、macroTimerFunc
if(如果setImmediate存在) {
macroTimerFunc =function(){
setImmediate(flushCallbacks);
};
}
elseif(如果MessageChannel存在) {
varchannel =newMessageChannel();
varport = channel.port2;
channel.port1.onmessage = flushCallbacks;
macroTimerFunc =function(){
port.postMessage(1);
};
}
else{
macroTimerFunc =function(){
setTimeout(flushCallbacks,0);
};
}
没啥好说的,最多记录一下 MessageChannel,更多内容就自己查啦
MessageChannel
简单来说,MessageChannel 用于创建了一个通信的管道,这个管道有两个端口
每个端口都可以通过postMessage发送数据
一个端口绑定onmessage回调,从另一个端口接收传过来的数据
不多说了,看下一个微任务
2、microTimerFunc
if(如果promise存在) {
varp =Promise.resolve();
microTimerFunc =function(){
p.then(flushCallbacks);
};
}else{
microTimerFunc = macroTimerFunc;
}
上面的宏微任务 函数都 出现了一个 flushCallbacks 的东西,下面会有
Vue 的任务队列
vue 自己维护了一个任务队列去配合 宏微任务使用,目的无非是几样
1、减少宏微任务的注册。尽量把所有异步代码放在一个 宏微任务中,减少消耗
2、加快异步代码的执行。我们知道,如果一个异步代码就注册一个宏微任务的话,那么执行完全部异步代码肯定慢很多
3、避免频繁地更新。Vue 中就算我们一次性修改多次数据,页面还是只会更新一次。就是因为这样,避免多次修改数据导致的多次频繁更新页面,让多次修改只用更新最后一次
下面就来说一下Vue 相关的实现
1、callbacks
callbacks 是一个数组,用于存放各种异步函数。比如
this.$nextTick(()=>{
console.log(1111)
})
就会把你设置的这个回调,放到 callbacks 数组中
callbacks.push(()=>{
console.log(1111)
})
既然 callbacks 是存放异步回调的,那么肯定有一个方法,是遍历 callbacks ,然后逐个执行其中存放的函数
没错,这个方法就是 flushCallbacks
2、flushCallbacks
方法灰常简单啊,大家肯定能看得懂啊
1、复制一遍 callbacks
2、把 原来 callbacks 清空
3、遍历 复制的 callbacks ,然后逐个执行
var callbacks = [];
var pending =false;
functionflushCallbacks(){
pending =false;
varcopies = callbacks.slice(0);
callbacks.length =0;
for(vari =0; i < copies.length; i++) {
copies[i]();
}
}
这个方法是 直接传给 上面设置的 宏任务函数 和 微任务函数的额
也就是说,宏任务和 微任务 的回调,都是执行这个 flushCallbacks
setTimeout(flushCallbacks)
嘿,我们之前有讲过,Vue 会控制当时执行栈的所有异步代码只注册一个 宏微任务
那么是怎么控制的呢?
还有还有,是怎么把 异步函数 存放到 callbacks 中的呢?
下面就需要请出我们的猪脚,nextTick 函数闪亮登场!!!
3、NextTick
Vue.nextTick =function(cb, ctx){
callbacks.push(function(){
cb && cb.call(ctx);
});
if(!pending) {
pending =true;
if(useMacroTask) {
macroTimerFunc();
}else{
microTimerFunc();
}
}
}
1、pending
通过判断 pending 来确定是否需要注册宏微任务
当第一次注册的时候,把 pending 设置为 true,表示任务队列已经在开始了,同一时期内无需注册了
然后在 任务队列 执行完毕之后,再把 pending 设置为 false(在 flushCallbacks 中)
2、callbacks
你可以看到,就是在这里进行存放 异步函数,还特地【包装】了一遍,为了绑定一个上下文对象
3、useMacroTask
Vue 怎么控制注册宏任务还是微任务呢?
没错,就是这个鬼东西了,设置为 true 时注册宏任务,设置为false 注册微任务
“在 2.6 版本中,已经不存在这个鬼东西,全部使用了微任务注册”
这个东西,在哪里用过啊?
在 注册 DOM 事件的时候用到,当事件回调执行的过程中,所有的异步代码都使用宏任务
你问为什么?内容太多,会有专篇分析
然后,关于 macroTimerFunc 和 microTimerFunc 上文已经讲过啦,可以回去看看