必须用微任务,因为其执行时机在同步任务结束后、DOM渲染前,且同一轮事件循环中所有微任务按序清空,从而实现依赖通知的批量合并、避免重复更新与中间态暴露。

JavaScript 中用微任务实现响应式数据的依赖通知,核心是把“触发依赖更新”这个动作延迟到当前同步任务结束后、DOM 渲染前执行,避免重复通知和保证响应式更新的批量性与一致性。最典型的做法是利用 Promise.resolve().then() 或 queueMicrotask() 包裹依赖通知逻辑。
为什么必须用微任务?
响应式系统中,一个数据可能被多个计算属性或视图依赖。如果每次 setter 都立即执行所有依赖的更新(比如重新求值 computed、触发 render),会导致:
- 同一轮更新中多次触发相同依赖(如 a → b → c,a 改变时 b 和 c 都可能被通知,c 又依赖 b)
- 视图重复渲染,性能浪费
- 状态不一致:中间态被暴露(比如 b 已更新但 c 还没更新)
微任务天然满足「本轮事件循环末尾、渲染前」的执行时机,且同一轮宏任务中所有微任务会**按顺序清空队列**,这为批量合并更新提供了基础。
简易响应式 + 微任务通知示例
以下是一个极简但可运行的响应式实现,展示如何用 queueMicrotask 延迟依赖通知:
立即学习“Java免费学习笔记(深入)”;
const depsMap = new WeakMap(); // target → { key → Set<effect> }
const effectStack = [];
<p>function track(target, key) {
const effect = effectStack[effectStack.length - 1];
if (!effect) return;</p><p>if (!depsMap.has(target)) depsMap.set(target, new Map());
const depMap = depsMap.get(target);
if (!depMap.has(key)) depMap.set(key, new Set());
depMap.get(key).add(effect);
}</p><p>function trigger(target, key) {
const depMap = depsMap.get(target);
if (!depMap) return;
const effects = depMap.get(key);
if (!effects) return;</p><p>// 收集所有待执行的 effect,去重后统一进微任务
const effectsToRun = new Set(effects);
queueMicrotask(() => {
effectsToRun.forEach(effect => effect());
});
}</p><p>// 响应式包装
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key);
return true;
}
});
}</p><p>// 副作用注册(类似 Vue 的 watchEffect)
function effect(fn) {
const wrapped = () => {
cleanup(wrapped);
effectStack.push(wrapped);
fn();
effectStack.pop();
};
wrapped();
}</p><p>function cleanup(effect) {
// 清除 effect 之前关联的所有依赖
for (const [target, depMap] of depsMap) {
for (const [key, effects] of depMap) {
effects.delete(effect);
}
}
}
关键点:
-
trigger不直接执行 effect,而是用queueMicrotask推入微任务队列 - 多个
set调用会积累多个微任务,但浏览器会合并执行(因为都在同一轮微任务队列里) -
cleanup在每次 effect 执行前清除旧依赖,确保依赖关系准确
和 Promise.then 的区别?
queueMicrotask 是更轻量、语义更明确的选择:
-
Promise.resolve().then(fn)会创建 Promise 对象,有额外开销 -
queueMicrotask(fn)是原生微任务 API(ES2021),无对象分配,性能更好,意图更清晰 - 两者执行时机完全一致,但
queueMicrotask更推荐用于纯调度场景
实际框架中的处理更精细
像 Vue 3 的 queueJob 或 React 的 scheduleUpdateOnFiber 并不只是简单推微任务,还会:
- 对 job 去重(相同组件更新只留最后一次)
- 按优先级排序(例如用户输入 > 动画 > 数据加载)
- 结合浏览器空闲时间(
requestIdleCallback)做异步分片 - 在微任务中启动更新流程,但真正的 patch/渲染可能进一步细分
但底层「将副作用通知延迟至微任务」这一设计原则始终未变——它是响应式系统保持高效与一致的关键支点。


















