重写window对象的setTimeout为何会触发最大调用栈溢出错误?
setTimeout覆盖异常问题分析与修复方案
问题根本原因
直接完全替换全局setTimeout的原生实现会触发运行环境的副作用,具体表现的原因如下:
- 所有前端运行环境都会依赖全局
setTimeout的原生异步能力做内部调度:codesandbox的沙箱运行时大量使用setTimeout实现热更新、执行状态同步、日志上报等逻辑,完全替换原生实现后会导致其内部调度逻辑异常,产生递归调用最终触发栈溢出。 - Chrome原生环境中,浏览器自身的页面调度、扩展运行逻辑会隐式调用全局
setTimeout,因此会出现非主动触发的随机lol打印。 - 自定义的
setTimeout没有遵循原生API的函数签名:原生setTimeout需要接收回调函数、延迟时间参数,执行后返回数值类型的定时器ID,无参箭头函数的实现会导致所有依赖该API参数、返回值的上层逻辑报错。
修复方案
方案1:保留原生能力的全局拦截
如果需要全局拦截setTimeout调用同时不影响原有功能,需要先缓存原生实现,在自定义逻辑执行后调用原生实现保证原有功能正常,示例代码如下:
// 缓存原生setTimeout实现 const nativeSetTimeout = window.setTimeout; // 自定义实现匹配原生函数签名 window.setTimeout = function(callback, delay = 0, ...args) { // 自定义业务逻辑 console.log("lol"); // 透传所有参数调用原生实现,返回标准定时器ID return nativeSetTimeout.call(window, callback, delay, ...args); };
方案2:完全替换原生实现
如果不需要保留原生setTimeout的异步能力,也要保证函数签名、返回值和原生对齐,避免上层逻辑报错,示例如下:
window.setTimeout = (callback, delay = 0, ...args) => { console.log("lol"); // 按需处理传入的回调等参数,返回符合规范的数值类型定时器ID return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); };
注意事项
- 非必要场景不建议修改全局内置对象的原生方法,极易和运行环境、第三方依赖的逻辑产生不可预知的冲突。
- 如果仅需要局部使用自定义延迟逻辑,建议单独封装工具函数,不要污染全局作用域。
内容的提问来源于stack exchange,提问作者Joji
相关产品推荐
相关产品推荐

