动态宏任务派发器是封装settimeout等机制的非标准api,核心是安全派发独立生命周期的回调:通过白名单函数名、深拷贝参数、闭包隔离避免注入风险;绑定清理契约防泄漏;用promise封装支持await、错误处理与取消;限定执行上下文防全局污染。

动态宏任务派发器本身不是标准 API,而是对 setTimeout、setInterval、postMessage 或自定义调度队列等机制的封装抽象。所谓“利用表达式安全注入具有独立生存期的回调”,核心诉求其实是:**在运行时动态生成并派发一个宏任务,且确保该回调不被意外覆盖、不因外部作用域变化失效、也不引入代码注入风险**。
避免字符串 eval 类注入,改用函数构造与闭包隔离
直接拼接字符串再用 eval 或 Function('...') 执行是高危操作,易导致 XSS 或逻辑失控。安全做法是:
- 将动态逻辑拆解为纯数据(如配置对象、参数列表)和预定义函数模板
- 用
Function构造时,仅传入受信参数名,不拼接用户输入值 - 用闭包包裹回调,使其持有独立作用域变量,不受后续外部修改影响
例如:
✅ 安全示例function createSafeMacroTask(handlerName, params) {
// handlerName 是白名单内的函数名(如 'onDataLoaded')
// params 是序列化后校验过的 JSON 数据
const safeParams = JSON.parse(JSON.stringify(params)); // 深拷贝防篡改
return () => {
// 闭包捕获当前 safeParams,生命周期独立于调用上下文
window[handlerName]?.(safeParams);
};
}
// 派发
setTimeout(createSafeMacroTask('updateUI', { id: 123, status: 'done' }), 0);
绑定明确的清理契约,防止内存泄漏
宏任务回调若引用了大型对象、DOM 节点或事件监听器,又未主动释放,就会延长其生存期,造成泄漏。应显式约定生命周期边界:
- 回调执行完毕即解除对大对象的引用(设为
null) - 若涉及定时器或轮询,回调内需调用
clearTimeout/clearInterval - 使用
WeakRef(现代环境)或弱映射缓存,避免强引用滞留
用 Promise 封装宏任务,统一控制完成态与错误边界
原生宏任务无返回值、不可 await、失败也不抛异常。包装一层可提升可控性:
- 返回 Promise,成功 resolve 回调结果,失败 reject 错误
- 在 Promise 的
finally中执行资源清理 - 配合
AbortSignal支持取消(适用于支持 AbortController 的环境)
例如:
✅ 可控封装function dispatchMacroTask(fn, delay = 0, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
try {
const result = fn();
resolve(result);
} catch (err) {
reject(err);
}
}, delay);
if (signal?.aborted) {
clearTimeout(timer);
reject(new Error('Aborted'));
} else {
signal?.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('Aborted'));
}, { once: true });
}
});
}
// 使用
dispatchMacroTask(() => console.log('run'), 100)
.catch(console.error);
限制执行上下文,避免污染全局或意外共享状态
动态派发的回调若默认在全局作用域执行,容易污染 window 或与其它模块冲突。建议:
- 统一指定执行上下文(如传入
{ context: {} }) - 用
with语句已废弃,改用Reflect.apply或fn.call(context, ...args) - 对回调函数做一次
bind或箭头函数封装,固化 this 和参数











