总结一下:vue中的事件队列分3种,vue内部实现中主要是把render函数的包裹体effect放到queue队列中。
/**
* vue中用了事件队列来调度vue内部的一些事件回调以及用户添加的事件,我们详细看下这部分的基础实现
*/
// then(flushAllCbs)正在执行的标记 标记是否正在刷新任务队列 初始和全部执行完事件后都是false 但是在执行中间某个job的时候又可能会添加新的job到同一队列中 这时候这个标记就起作用了
let isFlushing = false;
// 调用 Promise.then(flushAllCbs) 正式触发下个tick更新队列的标记
let isFlushPending = false;
// 普通事件队列 vue的包裹render函数的effect之类的都放在这里
const queue = [];
// 当前正在执行的job所处queue中的索引
let flushIndex = 0;
// 定义了2种回调事件 pre 和 post
// pre的优先级比queue高 post比queue低
// 2种事件最开始都是被推入到xxxPending队列中 然后被 xxxFlush操作 从pengding队列取出到xxxActive队列 再逐一执行 2者逻辑主体相同
// xxxFlushIndex语义同上
const pendingPreFlushCbs = [];
let activePreFlushCbs = null;
let preFlushIndex = 0;
const pendingPostFlushCbs = [];
let activePostFlushCbs = null;
let postFlushIndex = 0;
// 用于执行p.then的已就绪promise
const resolvedPromise = Promise.resolve();
// p.then 返回的新promise实例
let currentFlushPromise = null;
// pre队列刷新的时候有一种特殊调用情况 带有某个 parentJob 的参数然后刷新pre队列,这个时候在pre队列刷新过程中产生的queue job不与parentJob相同
// vue中用于组件更新的时候 详情见vue组件更新部分源码
let currentPreFlushParentJob = null;
// 在一次tick周期内的刷新过程中 某个job允许最多出现的次数
const RECURSION_LIMIT = 100;
// 把fn推入下个tick执行即可
function nextTick(fn) {
const p = currentFlushPromise || resolvedPromise;
return fn ? p.then(this ? fn.bind(this) : fn) : p;
}
// #2768
// Use binary-search to find a suitable position in the queue,
// so that the queue maintains the increasing order of job's id,
// which can prevent the job from being skipped and also can avoid repeated patching.
// 二分查找
function findInsertionIndex(job) {
// the start index should be `flushIndex + 1`
// 0 - 10 => 1 - 11 查找范围
let start = flushIndex + 1;
let end = queue.length;
const jobId = getId(job);
while (start < end) {
const middle = (start + end) >>> 1;
const middleJobId = getId(queue[middle]);
middleJobId < jobId ? (start = middle + 1) : (end = middle);
}
return start;
}
// vue中多次使用的用来把render更新effect推入job队列中
function queueJob(job) {
// the dedupe search uses the startIndex argument of Array.includes()
// by default the search index includes the current job that is being run
// so it cannot recursively trigger itself again.
// if the job is a watch() callback, the search will start with a +1 index to
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
// 1. 队列空 或者 (允许递归则忽略检测当前正在执行的job否则纳入检测范围)
// 且
// 2. 不等于 parentJob 这个场景分析见上文 一般情况这个场景都是null 成立
if ((!queue.length ||
!queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
job !== currentPreFlushParentJob) {
const pos = findInsertionIndex(job);
// 插入或者推入
if (pos > -1) {
queue.splice(pos, 0, job);
}
else {
queue.push(job);
}
// 尝试刷新队列
queueFlush();
}
}
function queueFlush() {
// 在一次刷新过程中 又有新的job被插入导致 queueFlush 又执行了 这2个标志就起作用了
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
// 移除任务
function invalidateJob(job) {
const i = queue.indexOf(job);
if (i > -1) {
queue.splice(i, 1);
}
}
// 推入cb到某个队列中的公共方法 解析同上文
function queueCb(cb, activeQueue, pendingQueue, index) {
if (!isArray(cb)) {
if (!activeQueue ||
!activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)) {
pendingQueue.push(cb);
}
}
else {
// if cb is an array, it is a component lifecycle hook which can only be
// triggered by a job, which is already deduped in the main queue, so
// we can skip duplicate check here to improve perf
pendingQueue.push(...cb);
}
queueFlush();
}
// 2个外部调用方法
function queuePreFlushCb(cb) {
queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex);
}
function queuePostFlushCb(cb) {
queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex);
}
// pre队列执行逻辑
function flushPreFlushCbs(seen, parentJob = null) {
if (pendingPreFlushCbs.length) {
// 取出队列
currentPreFlushParentJob = parentJob;
activePreFlushCbs = [...new Set(pendingPreFlushCbs)];
pendingPreFlushCbs.length = 0;
{
seen = seen || new Map();
}
for (preFlushIndex = 0; preFlushIndex < activePreFlushCbs.length; preFlushIndex++) {
{
checkRecursiveUpdates(seen, activePreFlushCbs[preFlushIndex]);
}
activePreFlushCbs[preFlushIndex]();
}
activePreFlushCbs = null;
preFlushIndex = 0;
currentPreFlushParentJob = null;
// recursively flush until it drains
// pre队列更新过程可能会产生新的pre事件 递归执行
flushPreFlushCbs(seen, parentJob);
}
}
function flushPostFlushCbs(seen) {
if (pendingPostFlushCbs.length) {
// 取出队列
const deduped = [...new Set(pendingPostFlushCbs)];
pendingPostFlushCbs.length = 0;
// #1947 already has active queue, nested flushPostFlushCbs call
if (activePostFlushCbs) {
activePostFlushCbs.push(...deduped);
return;
}
activePostFlushCbs = deduped;
{
seen = seen || new Map();
}
// 排序
activePostFlushCbs.sort((a, b) => getId(a) - getId(b));
for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {
{
checkRecursiveUpdates(seen, activePostFlushCbs[postFlushIndex]);
}
activePostFlushCbs[postFlushIndex]();
}
activePostFlushCbs = null;
postFlushIndex = 0;
}
}
// 获取任务id 通过effect产生的都有id 不然就是无穷大
const getId = (job) => job.id == null ? Infinity : job.id;
// 主刷新任务队列逻辑
function flushJobs(seen) {
// 标志置位
isFlushPending = false;
isFlushing = true;
{
seen = seen || new Map();
}
// 一次刷新中先执行pre队列
flushPreFlushCbs(seen);
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smaller
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
// 按照job创建的时间也就是id来排序处理 原因见上面的原文注释
queue.sort((a, b) => getId(a) - getId(b));
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job) {
if (true) {
checkRecursiveUpdates(seen, job);
}
// 执行
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
}
finally {
flushIndex = 0;
queue.length = 0;
// 最后处理post队列
flushPostFlushCbs(seen);
isFlushing = false;
currentFlushPromise = null;
// some postFlushCb queued jobs!
// keep flushing until it drains.
// queue刷新过程中如果又推入了新的任务或者post cb 再次执行
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
// 检查函数出现的次数
function checkRecursiveUpdates(seen, fn) {
if (!seen.has(fn)) {
seen.set(fn, 1);
}
else {
const count = seen.get(fn);
if (count > RECURSION_LIMIT) {
throw new Error(`Maximum recursive updates exceeded. ` +
`This means you have a reactive effect that is mutating its own ` +
`dependencies and thus recursively triggering itself. Possible sources ` +
`include component template, render function, updated hook or ` +
`watcher source function.`);
}
else {
seen.set(fn, count + 1);
}
}
}
// 总结:在一次tick中,pre队列在queue job之前执行,post总是在queue job之后执行。
/**
* 看下vue中如何使用这几个队列的:
* 1. render函数包裹体effect的调度函数 其实就是推入 queue job
* function createDevEffectOptions(instance) {
return {
scheduler: queueJob,
allowRecurse: true,
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
};
}
2. 实例的 $forceUpdate 方法就是调用组件实例的effect函数 也是推入queue job
const publicPropertiesMap = extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (shallowReadonly(i.props) ),
$attrs: i => (shallowReadonly(i.attrs) ),
$slots: i => (shallowReadonly(i.slots) ),
$refs: i => (shallowReadonly(i.refs) ),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (resolveMergedOptions(i) ),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy),
$watch: i => (instanceWatch.bind(i) )
});
3. watch的选择 默认是pre
job.allowRecurse = !!cb;
let scheduler;
if (flush === 'sync') {
scheduler = job;
}
else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
}
else {
// default: 'pre'
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job);
}
else {
// with 'pre' option, the first call must happen before
// the component is mounted so it is called synchronously.
job();
}
};
}
4. suspense 组件内使用
// no pending parent suspense, flush all jobs
if (!hasUnresolvedAncestor) {
queuePostFlushCb(effects);
}
5. pre队列在组件更新之前执行
const updateComponentPreRender = (instance, nextVNode, optimized) => {
nextVNode.component = instance;
const prevProps = instance.vnode.props;
instance.vnode = nextVNode;
instance.next = null;
updateProps(instance, nextVNode.props, prevProps, optimized);
updateSlots(instance, nextVNode.children);
// props update may have triggered pre-flush watchers.
// flush them before the render update.
flushPreFlushCbs(undefined, instance.update);
};
6. 高级API render函数执行完成dom视图更新 后
flushPostFlushCbs();
container._vnode = vnode;
*/
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。