qiankun沙箱必须劫持宏/微任务以防止内存泄漏和作用域污染,通过重写setTimeout、Promise等API,将任务绑定至沙箱生命周期与proxyWindow上下文,并维护独立的沙箱化任务队列实现隔离执行。

宏任务与微任务在沙箱中需被劫持重定向
微前端沙箱运行时,不能让第三方微应用的 setTimeout、Promise.then 等直接挂载到全局环境,否则卸载后仍可能触发回调,造成内存泄漏或作用域污染。因此,沙箱必须拦截并重写这些 API 的注册行为,使其回调绑定到沙箱自身的上下文生命周期中。
关键点在于:不是简单地“替换函数”,而是将任务注入逻辑改写为“沙箱感知型”——即任务注册时记录归属沙箱,执行时校验沙箱是否处于 active 状态。
-
宏任务重写示例:重定义
setTimeout,内部用一个代理函数包装回调,并在执行前检查sandbox.active === true;若沙箱已停用,则丢弃该回调 -
微任务重写难点:Promise 链无法直接拦截,需劫持
Promise.resolve().then的底层构造器(如通过Proxy包裹Promise构造函数),或更稳妥地使用queueMicrotask替代部分场景,并统一走沙箱微任务队列 -
恢复机制依赖状态快照:沙箱暂停时,不销毁定时器 ID 或 Promise 状态,而是将其暂存于
sandbox.pendingMacros和sandbox.pendingMicros中;恢复时重新注册并重置执行上下文(如绑定receiver到 proxyWindow)
沙箱内任务需绑定独立的执行上下文
普通宏/微任务默认在全局 window 下执行,而沙箱要求所有异步回调也运行在隔离的 proxyWindow 中。这就需要对任务执行环节做两层控制:
- 注册阶段:把原始回调包裹一层闭包,显式传入沙箱的
proxyWindow作为 this 或 context 参数 - 执行阶段:用
Reflect.apply(callback, proxyWindow, args)替代直接调用,确保this指向正确,且能访问沙箱内注入的变量(如__MICRO_APP_NAME__) - 特别注意
requestAnimationFrame和MutationObserver:它们虽属宏任务或微任务范畴,但涉及 DOM,需代理其回调并限制观察范围(如只监听沙箱容器内的节点)
微任务清空逻辑必须沙箱化
浏览器原生事件循环在每个宏任务结束后自动清空全局微任务队列。但在多沙箱共存时,这个行为会破坏隔离性——A 沙箱注册的 Promise.then 可能在 B 沙箱激活时执行,导致作用域错乱。
立即学习“Java免费学习笔记(深入)”;
解决方案是:禁用原生微任务调度路径,转而由沙箱自行维护一个微型微任务队列,并在沙箱 active 为 true 时手动触发清空。
- 劫持
Promise构造函数和原型方法,所有.then回调都 push 到sandbox.microTaskQueue - 在沙箱
activate()时启动一个微任务循环:queueMicrotask(() => sandbox.flushMicroTasks()),该函数递归执行队列直到为空 - 卸载时清空队列,同时将未 resolve 的 Promise 置为 pending 状态(避免意外触发),恢复时根据状态决定是否重放
实际工程中常被忽略的边界情况
很多沙箱实现能处理基础 setTimeout 和 Promise,但以下场景容易出问题:
-
嵌套微任务:如
Promise.resolve().then(() => Promise.resolve().then(...)),需确保内层回调也进入当前沙箱队列,而非逃逸到全局 -
跨沙箱通信产生的任务:比如主应用通过
postMessage触发子应用逻辑,子应用再调用setTimeout—— 此时任务应归属子沙箱,而非主沙箱 -
第三方库自建任务队列:例如 Lodash 的
debounce、React 的useEffect清理函数,需配合沙箱的patch方法统一接管 -
Node.js 环境差异:若微前端框架支持服务端渲染(SSR),
process.nextTick必须单独劫持,因其优先级高于 Promise,且不经过浏览器事件循环


















