携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情
前言
源码分析文章看了很多,也阅读了至少两遍源码。终归还是想自己写写,作为自己的一种记录和学习。重点看注释部分和总结,其余不用太关心,通过总结对照源码回看过程和注释收获更大
定义
nextTick
定义的有两处地方,所以可以通过Vue.nextTick
和this.$nextTick
两种方式调用
// src/core/global-api/index.js
Vue.nextTick = nextTick;
// src/core/instance/render.js
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this);
};
nextTick
实现
首先是嗅探环境
,依次检测Promise.then
(微任务)->MutationObserver
(微任务)->setImmediate
(宏任务IE)->setTimeout
(宏任务),依次去找,如果存在就使用它来异步执行
// src/core/util/next-tick.js
export let isUsingMicroTask = false;
// 存放异步调用任务
const callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
// 循环执行队列中的任务
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}
// 嗅探环境
let timerFunc;
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
if (isIOS) setTimeout(noop);
};
isUsingMicroTask = true;
} else if (
!isIE &&
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb?: Function, ctx?: Object) {
let _resolve;
callbacks.push(() => {
if (cb) {
// 为了保证队列中有出错的任务不会影响到其他任务执行,故采用try...catch的形式
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
// 确保其中的逻辑只执行一次
if (!pending) {
pending = true;
timerFunc();
}
// 如果有Promise,则nextTick支持then写法 this.$nextTick().then()
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve) => {
_resolve = resolve;
});
}
}
watcher 中的异步更新
在更新时,可能更改了多次,但是多次的更改调用的是同一个watcher,所以先将其去重后缓存起来,统一做更新,如此一来就不用更新多次了
// src/core/observer/watcher.js
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 异步队列机制
queueWatcher(this)
}
}
// src/core/observer/scheduler.js
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// beforeUpdate生命周期
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run() // run更新视图 调用回调等
}
...
}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// watcher去重,如果没有就加入
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 变量防抖,初次执行,之后不执行,在将watcher添加完之后,会异步执行nextTick执行watcher的run方法
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
注意点
nextTick
有微任务,微任务是在渲染前执行,为什么还可以拿到最新的$el
---取得是内存中的$el
- 如果在赋值之前执行
this.$nextTick
再赋值,此时拿到的是旧的值,因为nextTick是一个队列
总结
nextTick
把要执行的任务推入到一个队列中,首先是嗅探环境
,依次检测Promise.then
->MutationObserver
->setImmediate
->setTimeout
,依次去找,如果存在就使用它来异步执行。watcher中的更新也是异步更新,会将watcher放入队列中,之后调用nextTick来异步执行。