script onerror降级需防重、分离加载与执行、区分失败类型,否则会导致无限循环或Safari静默失效;fallback必须用新script元素、检查readyState、二级不重试,且onerror中应使用event.target而非this。

script 标签的 onerror 事件不是“写了就能降级”,不加防重、不分离加载与执行、不区分失败类型,轻则 fallback 失败后无限循环,重则 Safari 下静默失效、200 状态码返回 HTML 错误页却完全捕获不到。
为什么直接写 this.src = 'fallback.js' 会崩
这是最常见也最危险的写法。它在三种典型场景下直接失效:
- fallback 脚本也 404 →
onerror再次触发 → 无限循环,内存飙升甚至页面卡死 - 动态插入的
script元素,在 iOS 15.4–16.2 的 Safari 中onerror根本不触发(已知 bug) - CDN 返回 200 状态码的 HTML 错误页(如 Nginx 自定义 404 页面),浏览器认为“加载成功”,但执行时报
SyntaxError——onerror完全不响应
安全 fallback 必须满足的四个条件
真正能上线的脚本降级逻辑,得同时满足:
- 每个
script加唯一data-id,并在全局(如window.__triedFallbacks)记录已尝试 ID,防止重复 fallback - fallback 脚本必须新建
document.createElement('script')插入,绝不能复用原元素 - 插入前检查
document.readyState:若为'loading',需延迟到DOMContentLoaded后再插入,否则 DOM 尚未就绪,脚本执行报错 - fallback 脚本自身也要绑定
onerror,但只打日志、不上报、不重试 —— 二级 fallback 已是底线,三级毫无意义
onerror 回调里 this 指向谁?别依赖它
内联写法 onerror="handleError(event)" 中,this 指向 window,不是 script 元素。只有 onerror="this.src='...'" 这种直接赋值才让 this 是当前元素 —— 但这种写法本身就不安全。
稳妥做法是统一用事件参数:
function handleError(event) {
const script = event.target;
if (!script.dataset.fallbackTried) {
script.dataset.fallbackTried = '1';
const fallback = document.createElement('script');
fallback.src = 'local-vendor.js';
fallback.onerror = () => console.warn('Fallback script also failed:', script.src);
document.head.appendChild(fallback);
}
}
注意:event.target 在 onerror 中始终是出问题的 script 元素,比 this 可靠得多。
替代方案:window.addEventListener('error', ..., true) 怎么用才不误伤
全局监听能捕获动态创建的 script 加载失败,但极易把 JS 运行时错误(ErrorEvent)和资源加载失败(普通 Event)混在一起:
- 判断依据只能是:
event.target.tagName === 'SCRIPT' && !(event instanceof ErrorEvent) - 必须加
true(捕获阶段),否则可能错过极快失败的请求 - 不要在这里做
location.reload()或弹窗 —— 用户可能刚填完表单;建议仅记录路径 + 触发次数,连续两次同 URL 失败再上报或加降级类
复杂点在于:你永远不知道哪个 script 是关键路径,哪个只是埋点 SDK。真要容错,得结合构建时标记(如 data-critical="true")分层处理 —— 这部分常被忽略,但恰恰决定 fallback 是否有效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











