Vue2源码解析:nextTick实现原理
创作时间:
作者:
@小白创作中心
Vue2源码解析:nextTick实现原理
引用
1
来源
1.
https://www.cnblogs.com/wanglei1900/p/18794244
Vue.js中的nextTick是一个非常重要的API,它允许我们在DOM更新循环结束之后执行延迟回调。这个机制在处理异步更新队列时非常有用,可以确保在DOM更新完成后执行特定的代码。本文将深入解析Vue2中nextTick的源码实现,帮助读者理解其背后的原理和具体实现方式。
/* globals MutationObserver */
import { noop } from "shared/util";
import { handleError } from "./error";
import { isIE, isIOS, isNative } from "./env";
// 微任务使用标识
export let isUsingMicroTask = false;
// 存储回调队列
const callbacks: Array<Function> = [];
// 释放锁
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]();
}
}
// 此处为使用微任务的异步延迟包装器实现
// 在2.5版本中曾采用宏任务与微任务结合的方案
// 但该方案存在状态变更恰好在重绘前发生的隐性问题
// (例如案例#6813中的输入输出过渡效果异常)
// 同时在事件处理函数中使用宏任务会引发不可规避的异常行为
// (如案例#7109、#7153、#7546、#7834、#8109所示)
// 因此当前版本统一使用微任务实现
// 此方案的主要缺陷在于某些场景下
// 微任务优先级过高可能导致其在预期顺序事件间触发
// (如案例#4521、#6690,已提供临时解决方案)
// 甚至在同事件冒泡阶段之间触发(案例#6566)
// 核心:异步执行策略选择
let timerFunc;
// nextTick的实现机制基于微任务队列的调用方式,可通过原生Promise.then或MutationObserver两种途径实现
// 虽然MutationObserver拥有更广泛的浏览器支持,但在iOS >=9.3.3版本的UIWebView环境中
// 当通过触摸事件处理程序触发时存在严重缺陷。该缺陷表现为多次触发后会完全失效
// 因此,当原生Promise可用时,我们将优先采用Promise方案:
// 判断环境按照顺序降级决定使用 Promise微任务 >>> MutationObserver微任务 >>> setImmediate宏任务 >>> setTimeout宏任务
if (typeof Promise !== "undefined" && isNative(Promise)) {
const p = Promise.resolve();
timerFunc = () => {
// Promise.resolve().then() 将 flushCallbacks 放入微任务队列
p.then(flushCallbacks);
// 在存在兼容性问题的UIWebView环境中,Promise.then虽不会完全失效,
// 但可能陷入回调被推入微任务队列却无法及时清空的异常状态,直到浏览器
// 需要执行其他操作(例如处理定时器)才会恢复。为此,我们通过添加空定时器
// 来"强制"触发微任务队列的清空机制。
// iOS WebView兼容处理:强制刷新微任务队列
if (isIOS) setTimeout(noop);
};
isUsingMicroTask = true;
} else if (
!isIE &&
typeof MutationObserver !== "undefined" &&
(isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === "[object MutationObserverConstructor]")
) {
// 在原生Promise不可用的环境中,使用MutationObserver作为替代方案,
// 例如PhantomJS、iOS7及Android 4.4等环境
// (参考问题#6466,MutationObserver在IE11中存在兼容性问题)
// 通过修改文本节点内容触发 MutationObserver(微任务)
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)) {
// 当其他方案不可行时,回退至setImmediate实现。
// 从技术原理上,该方案利用了宏任务队列机制,
// 但在执行效率及时序控制方面仍优于setTimeout方案。
// 优于 setTimeout,但仅IE/Node.js支持。
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 降级兜底方案 setTimeout.
// 兼容性最好的宏任务,但可能有4ms的最低延迟。
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(): Promise<void>;
export function nextTick<T>(this: T, cb: (this: T, ...args: any[]) => any): void;
export function nextTick<T>(cb: (this: T, ...args: any[]) => any, ctx: T): void;
/**
* @internal
*/
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
// 这里使用了闭包
// 每个nextTick调用需独立绑定自身的resolve函数,避免多个 Promise 实例间的冲突
let _resolve;
// 推进回调队列
callbacks.push(() => {
if (cb) {
try {
// 执行用户回调
cb.call(ctx);
} catch (e: any) {
// 异常捕获
handleError(e, ctx, "nextTick");
}
} else if (_resolve) {
// 兼容写法 支持持无回调的Promise用法
// 这里使用了 “先注册依赖,后初始化状态” 模式(因为callbacks都是异步任务)
_resolve(ctx);
}
});
// 如果还未启动异步任务
if (!pending) {
// 加锁
pending = true;
timerFunc();
}
// 不传回调时返回promise
if (!cb && typeof Promise !== "undefined") {
return new Promise(resolve => {
// 将 Promise 的resolve方法绑定至_resolve变量
_resolve = resolve;
});
}
}
nextTick(cb)
-> 将cb推入callbacks队列
-> 如果未启动异步任务(pending=false)
-> 调用timerFunc
-> 微任务(Promise/MutationObserver)或宏任务(setImmediate/setTimeout)
-> 异步任务触发flushCallbacks
-> 执行所有callbacks中的回调
热门推荐
菠菜嘌呤含量低,痛风患者可放心食用
杭州:沿江发展与城西崛起并行,跻身超大城市行列
冻干粉适合晚上用还是白天用呢
图文解析|园林史-拙政园平面图以及轴线的处理
中国园林之《苏园六纪》:第一集《吴门烟水》
中国古典园林文化——江南精华
探秘茂名出发至新疆可可托海全程攻略与必游景点推荐
冬至大过年!茂名人餐桌上的这些美食,你吃过几种?
2024茂名马拉松:一条串起12处景点的"零距离"亲海赛道
杭州秋季旅游攻略:赏秋景点、交通方式一文全知
新音频书解读古诗词:10小时讲述诗词背后的故事
痛风患者能吃香菜吗?专家解析:属于低嘌呤食物
香菜属于低嘌呤食物,痛风患者可适量食用
香菜嘌呤含量仅21毫克,痛风患者可适量食用
改善凸嘴不用手术:唇肌训练和功能性矫正各有优劣
牙性凸嘴可正畸,骨性凸嘴需手术:专业医生这样建议
凸嘴改善指南:从面部锻炼到专业矫正的全方位方案
31岁矫正凸嘴:从不敢笑到月拒数人追求
告别凸嘴和露龈笑:拔牙、骨钉、隐形矫正全攻略
婚姻幸福的秘诀:性格差异如何变成加分项
详解丹参片:心血管疾病良药,多种食用方式与禁忌
精选10味抗癌中药:功效、应用与禁忌详解
格列齐特都有哪些副作用
分子间氢键对分子晶体熔沸点的影响
掌握这12组配色,轻松穿出夏日高级感
运城盐池:千年盐湖的绿色蝶变
盐池县产盐环境影响评估报告出炉:生态压力凸显,可持续发展成关键
网络流行语背后,藏着啥秘密?
全实景搭建+本土演员:Netflix版<百年孤独>获全球好评
2025年IP改编剧占比超六成,多元化开发成新趋势