如何将被第三方库篡改的window.Promise还原为原生实现?
全局Promise被篡改导致第三方库运行异常解决方案
问题根因
instanceof 关键字的判断逻辑是校验待检测对象的原型链是否存在于右侧构造函数的prototype原型对象上。库a加载时替换了全局window.Promise构造函数引用后,库b内部创建的原生Promise实例原型链和被篡改后的Promise构造函数不匹配,就会出现instanceof Promise返回false的问题。
可行实施方案
方案1:提前缓存原生Promise,加载库b时临时还原全局(适配现有$.getScript加载逻辑)
这个方案改动最小,完全适配当前动态加载逻辑,核心要求是缓存原生Promise的代码必须放在所有第三方库加载之前执行,否则缓存到的还是被篡改的版本。
- 在页面
<head>标签内、所有其他脚本加载前,先缓存原生构造函数:<script> // 抢在所有第三方库加载前缓存原生Promise,这个位置不能后置 const _nativeWindowPromise = window.Promise; </script> <!-- 后面再放库a、jQuery、KnockoutJS等其他脚本引用 --> - 修改点击触发加载库b的逻辑,加载前临时还原全局Promise,库b初始化完成后恢复库a的自定义Promise,避免影响库a正常运行:
// 点击事件触发加载逻辑 $('.load-libb-trigger').on('click', function () { // 暂存当前被库a篡改的Promise const currentPromise = window.Promise; // 还原为原生实现 window.Promise = _nativeWindowPromise; $.getScript('/path/to/lib-b.es6.js', function () { // 等库b完成所有初始化逻辑后,恢复库a的Promise实现 // 注意:如果库b存在异步初始化逻辑,要等初始化完成后再执行恢复操作 window.Promise = currentPromise; // 后续执行库b相关的业务调用逻辑即可 }); });
方案2:闭包隔离加载(零侵入,无全局污染,推荐长期使用)
这个方案不需要来回修改全局对象,完全隔离两个库的运行环境,不会出现互相影响的问题,不需要担心恢复全局的时机不对导致的异常。
把原来用$.getScript加载库b的逻辑替换为闭包执行方式:
$('.load-libb-trigger').on('click', async function () { // 拉取库b的脚本源码文本 const libBSource = await fetch('/path/to/lib-b.es6.js').then(resp => resp.text()); // 创建独立执行作用域,把提前缓存的原生Promise作为作用域内的Promise构造函数传入 // 库b内部所有Promise引用都会优先使用传入的原生版本,完全不读取全局window.Promise (new Function('Promise', libBSource))(_nativeWindowPromise); // 执行完成后全局window.Promise仍为库a的自定义实现,两个库运行互不干扰 });
如果库b是团队自行维护的TypeScript项目,还可以直接在编译打包阶段做作用域封装,打包时把原生Promise作为依赖注入到库b的作用域中,后续使用完全不需要额外处理加载逻辑。
注意事项
- 浏览器没有提供API可以在原生构造函数被覆盖后重新获取原生引用,所以提前缓存的步骤必须放在所有可能修改全局对象的第三方库加载之前,否则缓存逻辑无效。
- 如果库b内部还存在其他对原生构造函数的
instanceof判断(比如Map、Set、ArrayBuffer等),可以用同样的方式提前缓存原生构造函数,通过闭包传入隔离环境,避免同类问题。 - 低版本jQuery的
$.getScript默认会关闭请求缓存,可提前配置$.ajaxSetup({cache: true})避免重复拉取脚本产生不必要的性能损耗。
内容的提问来源于stack exchange,提问作者curious_debugger
相关产品推荐
相关产品推荐

