navigator.onLine仅检测系统网卡状态,无法确认服务器连通性;应使用带超时和缓存禁用的fetch探测/favicon.ico来判断真实网络可用性。

navigator.onLine 只能告诉你系统有没有启用网卡,不能判断能不能连上你的服务器。它返回 true 却发不出请求、页面白屏、接口全 TypeError: Failed to fetch——这太常见了,不是代码写错了,是它本来就不负责这事。
为什么 navigator.onLine 会误报?
这个属性只读取操作系统网络接口的启用状态:Wi-Fi 开关打开了、以太网适配器已启用、macOS 显示“已连接到 Wi-Fi”……它就返回 true。但以下情况它完全感知不到:
- 路由器断电或 WAN 口没连外网
- DNS 解析失败(比如
dig your-api.com超时) - 代理配置错误或被防火墙拦截
- 你连的是个纯局域网热点(无互联网出口)
更麻烦的是:navigator.onLine 在 file:// 协议下恒为 true;某些旧版 Android WebView 也永远返回 true;Chrome 启动瞬间未联网也可能返回 true。
用 fetch('/favicon.ico', { method: 'HEAD' }) 做真实连通性探测
真正要确认“能不能调通后端”,必须发一个轻量 HTTP 请求。选 /favicon.ico 是因为它体积小、无副作用、同源、几乎每个网站都有(404 也说明请求发出去并收到了响应)。
立即学习“前端免费学习笔记(深入)”;
- 务必加
cache: 'no-store',防止 CDN 或浏览器缓存伪造成功 - 必须用
AbortController或AbortSignal.timeout(3000)设超时,否则 DNS 卡住会冻结整个表单提交流程 - 判断依据不是
response.status === 200,而是response.ok || response.status === 404——只要拿到响应,就说明网络栈通了 - 不要探测第三方地址(如
https://google.com),跨域、策略拦截、DNS 慢都会误判
示例函数:
function checkRealConnection() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
return fetch('/favicon.ico', {
method: 'HEAD',
cache: 'no-store',
signal: controller.signal
})
.then(res => {
clearTimeout(timeoutId);
return res.ok || res.status === 404;
})
.catch(() => false);
}
online/offline 事件只能做 UI 辅助,不能初始化状态
这两个事件响应快,适合禁用按钮、显示“离线中”提示,但有明显缺陷:
- 首次加载时还没触发事件,
navigator.onLine的初始值不可信 - 事件可能延迟数秒才触发(尤其 macOS 切热点、iOS Safari 后台标签页)
- 不会冒泡,只能绑定在
window上,且必须在DOMContentLoaded后立即执行,否则错过首次切换 - 组件卸载时若不调用
removeEventListener,可能触发已销毁实例的方法
正确做法:页面加载完成立刻调一次 checkRealConnection() 获取首屏真实状态,再用事件做后续更新;别等事件来“定乾坤”。
最常被忽略的一点:网络探测不是一锤子买卖。表单提交前要兜底再测一次,用户点击“重试”时也要重新探测——因为 online 事件触发后,DNS 可能刚崩,服务可能刚挂,而你 UI 已经变绿了。



















