弱网下外部脚本静默失败,根源是浏览器单次加载无重试无超时,超时后仅导致ReferenceError;必须用onerror+fallback+分层降级+兼容性检测主动兜底。

外部脚本在弱网下加载失败,不是“报错”而是静默失败——ReferenceError或TypeError: xxx is not defined只是表象,根源是脚本压根没进页面。
为什么<script src="xxx.js">在弱网下会报错而不是重试</script>
浏览器对<script src="xxx.js"></script>的加载行为是单次、无重试、无超时控制的。网络慢或中断时,它会一直等,直到连接超时(通常 30–60 秒),期间不触发任何事件;超时后也不抛异常,只是让后续依赖它的代码执行时报ReferenceError。
- 默认不触发
onerror——除非你显式写了onerror属性,否则控制台连“加载失败”提示都没有 -
async或defer不影响加载失败逻辑,只影响执行时机 - CDN 或第三方脚本(如
lodash.min.js)在弱网下首字节延迟高,极易卡在 DNS + TCP + TLS 阶段
必须加 onerror + fallback 的真实写法
不能只靠try/catch包裹调用代码——那捕获不到“变量未定义”,只能捕获运行时错误。真正要拦截的是资源加载失败本身。
- 给
<script></script>标签加onerror和onload:<script src="https://cdn.example.com/lodash.min.js" onerror="window._ = null; console.warn('lodash load failed')" onload="console.log('lodash ready')"></script> - 配合全局检查:在 DOM 就绪后判断
window._是否存在,不存在则降级使用轻量 polyfill 或禁用依赖功能 - 避免用
document.write()动态插入 fallback 脚本——它会清空整个文档流,低配安卓机上必白屏
比重试更有效的弱网兜底策略
重试脚本(比如用setTimeout反复创建<script></script>)在弱网下反而加重负担,且无法解决首屏阻塞问题。实际有效的是分层降级:
立即学习“前端免费学习笔记(深入)”;
- 首屏 JS 逻辑拆成两层:核心 DOM 操作(无外部依赖)立即执行;非核心功能(图表、富文本)延迟到
DOMContentLoaded后再按需加载 - 第三方 SDK(如统计、客服)统一用
fetchpriority="low",并包裹在if (navigator.onLine && window.performance?.navigation?.type !== 1)里,避免冷启动时抢占带宽 - 关键框架(如 Vue/React)改用内联小体积 runtime + 动态
import():if (!window.Vue) { import('/js/vue.runtime.esm-browser.js').catch(() => { /* 降级为原生组件 */ }); }
容易被忽略的兼容性陷阱
很多“兜底方案”在旧版安卓 WebView 或 Safari 15.2 之前失效,不是代码写错,而是 API 不可用:
-
import()在 Android 7–9 的 WebView 中不可用,需提前检测:typeof import === 'function' -
onerror在部分 UC 浏览器中不触发 script 加载失败,得配合setTimeout计时兜底(比如 8s 未完成就判定失败) -
fetchpriority仅在 Chromium 101+ 和 Safari 16.4+ 支持,低版本需用<link rel="preload">替代,但注意crossorigin必须带上,否则字体/模块脚本会因 CORS 失败
弱网下的脚本加载问题,本质不是“怎么让它快”,而是“怎么让它不拖垮首屏”。所有防御性写法都得围绕一个事实:浏览器不会告诉你它卡住了,你得自己盯住它。



















