一、Vue.nextTick 内部逻辑
在执行 initGlobalAPI(Vue)
初始化 Vue 全局 API 中,这么定义 Vue.nextTick
。
function initGlobalAPI(Vue) {
//...
Vue.nextTick = nextTick;
}
可以看出是直接把 nextTick
函数赋值给 Vue.nextTick
,就可以了,非常简单。
二、vm.$nextTick 内部逻辑
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
可以看出是 vm.$nextTick
内部也是调用 nextTick
函数。
三、前置知识
nextTick
函数的作用可以理解为异步执行传入的函数,这里先介绍一下什么是异步执行,从 JS 运行机制说起。
1、JS 运行机制
JS 的执行是单线程的,所谓的单线程就是事件任务要排队执行,前一个任务结束,才会执行后一个任务,这就是同步任务,为了避免前一个任务执行了很长时间还没结束,那下一个任务就不能执行的情况,引入了异步任务的概念。JS 运行机制简单来说可以按以下几个步骤。
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,会把其回调函数作为一个任务添加到任务队列中。
- 一旦执行栈中的所有同步任务执行完毕,就会读取任务队列,看看里面有那些任务,将其添加到执行栈,开始执行。
- 主线程不断重复上面的第三步。也就是常说的事件循环(Event Loop)。
2、异步任务的类型
nextTick
函数异步执行传入的函数,是一个异步任务。异步任务分为两种类型。
主线程的执行过程就是一个 tick,而所有的异步任务都是通过任务队列来一一执行。任务队列中存放的是一个个的任务(task)。规范中规定 task 分为两大类,分别是宏任务(macro task)和微任务 (micro task),并且每个 macro task 结束后,都要清空所有的 micro task。
用一段代码形象介绍 task的执行顺序。
for (macroTask of macroTaskQueue) {
handleMacroTask();
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
参考Vue3源码视频讲解:进入学习
在浏览器环境中,
常见的创建 macro task 的方法有
- setTimeout、setInterval、postMessage、MessageChannel(队列优先于setTimeiout执行)
- 网络请求IO
- 页面交互:DOM、鼠标、键盘、滚动事件
- 页面渲染
常见的创建 micro task 的方法 - Promise.then
- MutationObserve
- process.nexttick
在 nextTick
函数要利用这些方法把通过参数 cb
传入的函数处理成异步任务。
三、 nextTick 函数
var callbacks = [];
var pending = false;
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();
}
if (!