JavaScript檢測網(wǎng)絡連接狀態(tài)以及網(wǎng)絡測速的具體方案
引言
在實際開發(fā)項目中,有時候需要拿到用戶網(wǎng)絡狀態(tài),然后根據(jù)用戶的網(wǎng)絡情況進行優(yōu)化。常見的網(wǎng)絡狀態(tài)有離線 offline、WiFi、2G/3G、4G、5G 等,但在實際開發(fā)中,我們一般會劃分成斷網(wǎng)、弱網(wǎng)、強網(wǎng)三個等級,并針對等級進行優(yōu)化。比如在視頻播放時,可以根據(jù)不同網(wǎng)絡情況給用戶分別默認播放不同清晰度的視頻(高清、標清、流暢等)。
那么 JavaScript 中如何檢測用戶網(wǎng)絡狀態(tài)呢?
1、網(wǎng)絡狀態(tài):在線 online Or 離線 offline?
HTML5 規(guī)范提供了 navigator.onLine 來拿到瀏覽器的在線狀態(tài)。該屬性返回一個布爾值,true 表示在線,false 表示離線。同時提供了 online 和 offline 事件,通過監(jiān)聽這兩個事件,可以動態(tài)監(jiān)聽到網(wǎng)絡連接狀態(tài)變化。
// 監(jiān)聽網(wǎng)絡連接狀態(tài)
window.addEventListener('online', () => {
console.log('網(wǎng)絡已連接');
// 觸發(fā)數(shù)據(jù)同步...
});
window.addEventListener('offline', () => {
console.log('網(wǎng)絡已斷開');
// 進入離線模式...
});
// 實時獲取當前狀態(tài)
console.log('當前網(wǎng)絡狀態(tài):', navigator.onLine ? '在線' : '離線');
但是 navigator.onLine 有一定的局限性,比如在某些情況下,網(wǎng)絡是連接的,它也返回 true 了,但實際上無法訪問你的后端 API:
- WiFi連接了但無互聯(lián)網(wǎng)訪問(比如公共圖書館需要登錄認證的公共 WiFi)。
- 網(wǎng)絡延遲極高(理論上在線,但請求超時)。
- DNS解析失敗。
我們需要更加準確的判斷用戶此刻是否網(wǎng)絡能正常訪問服務端,這就需要做心跳檢測。
2、心跳檢測
顧名思義,心跳檢測就像人的心跳一樣,每隔一段時間跳動一下,告訴外界"我還活著"。它是一種用于判斷連接對端(如服務器或客戶端)是否存活的機制。它常常用于 WebSocket 連接,用來解決非正常關(guān)閉造成的"死連接"問題。我們這里可以借用它檢測客戶端“是否存活”的能力,來判斷客戶端的網(wǎng)絡是否正常訪問后端 API。
心跳檢測客戶端代碼:
// client.js
class NetworkMonitor {
constructor(options = {}) {
this.options = {
pingUrl: '/api/ping', // 心跳檢測接口
interval: 30000, // 30秒檢測一次
timeout: 5000, // 5秒超時
onStatusChange: null, // 狀態(tài)變化回調(diào)
...options
};
this.isOnline = navigator.onLine; // 當前網(wǎng)絡狀態(tài)
this.checking = false; // 是否正在檢測
this.timer = null; // 定時器
this.consecutiveFailures = 0; // 連續(xù)失敗次數(shù)
this.maxFailures = 2; // 最大允許失敗次數(shù)
this.init();
}
init() {
// 監(jiān)聽瀏覽器原生事件
window.addEventListener('online', () => this.handleBrowserOnline());
window.addEventListener('offline', () => this.handleBrowserOffline());
// 啟動心跳檢測
this.startHeartbeat();
}
startHeartbeat() {
this.stopHeartbeat();
this.timer = setInterval(() => this.checkNetwork(), this.options.interval);
// 立即執(zhí)行一次
this.checkNetwork();
}
stopHeartbeat() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
async checkNetwork() {
if (this.checking) return;
this.checking = true;
const startTime = Date.now();
try {
// 使用fetch發(fā)起一個輕量級請求
const response = await fetch(this.options.pingUrl, {
method: 'HEAD', // 只獲取頭部,減少數(shù)據(jù)傳輸
mode: 'no-cors', // 如果跨域且無需讀取響應
cache: 'no-cache',
headers: {
'X-Network-Check': 'ping'
}
});
// 請求成功
this.handleSuccess();
} catch (error) {
// 請求失?。ǔ瑫r、網(wǎng)絡錯誤等)
this.handleFailure();
} finally {
this.checking = false;
}
}
handleSuccess() {
// 重置失敗計數(shù)
this.consecutiveFailures = 0;
// 如果之前是離線,現(xiàn)在變?yōu)樵诰€
if (!this.isOnline) {
this.setOnlineStatus(true);
}
}
handleFailure() {
this.consecutiveFailures++;
// 連續(xù)失敗達到閾值,判定為離線
if (this.consecutiveFailures >= this.maxFailures && this.isOnline) {
this.setOnlineStatus(false);
}
}
handleBrowserOnline() {
// 瀏覽器說在線了,但我們?nèi)孕栩炞C真正的API可達性
console.log('瀏覽器報告網(wǎng)絡已連接,開始驗證...');
this.checkNetwork();
}
handleBrowserOffline() {
// 瀏覽器明確說離線,直接更新狀態(tài)
this.setOnlineStatus(false);
}
setOnlineStatus(status) {
if (this.isOnline !== status) {
this.isOnline = status;
console.log(`網(wǎng)絡狀態(tài)變更為: ${status ? '在線' : '離線'}`);
// 觸發(fā)回調(diào)
if (this.options.onStatusChange) {
this.options.onStatusChange(status);
}
// 觸發(fā)自定義事件,方便其他地方監(jiān)聽
window.dispatchEvent(new CustomEvent('network-status-change', {
detail: { isOnline: status }
}));
}
}
// 手動觸發(fā)一次檢測
async checkNow() {
return this.checkNetwork();
}
// 銷毀
destroy() {
this.stopHeartbeat();
window.removeEventListener('online', this.handleBrowserOnline);
window.removeEventListener('offline', this.handleBrowserOffline);
}
}
// 使用示例
const monitor = new NetworkMonitor({
pingUrl: 'https://your-api.com/api/ping',
interval: 15000, // 15秒檢測一次
timeout: 3000,
onStatusChange: (isOnline) => {
if (isOnline) {
// 網(wǎng)絡恢復,觸發(fā)數(shù)據(jù)同步
syncWithServer();
showToast('網(wǎng)絡已恢復,正在同步數(shù)據(jù)...');
} else {
// 網(wǎng)絡斷開,進入離線模式
enableOfflineMode();
showToast('網(wǎng)絡已斷開,進入離線模式');
}
}
});
心跳檢測服務端代碼:
// server.js
const express = require('express');
const app = express();
const port = 3000;
// 心跳檢測接口
app.head('/api/ping', (req, res) => {
// 可選:記錄心跳日志
console.log(`[${new Date().toISOString()}] 收到心跳檢測請求 from ${req.ip}`);
// 可選:檢查自定義頭
if (req.headers['x-network-check'] === 'ping') {
console.log('網(wǎng)絡健康檢查請求');
}
// 只需返回 200 狀態(tài)碼即可
res.status(200).end();
});
// 如果需要 GET 也支持(某些場景可能需要)
app.get('/api/ping', (req, res) => {
// 可以返回一些輕量級數(shù)據(jù)
res.json({
status: 'ok',
timestamp: Date.now(),
message: 'success'
});
});
// 啟動服務器
app.listen(port, () => {
console.log(`網(wǎng)絡監(jiān)控服務運行在 http://localhost:${port}`);
});
3、網(wǎng)速檢測
3.1 navigator.connection
HTML5 規(guī)范提供了 navigator.connection 可以獲取網(wǎng)絡類型 effectiveType 和網(wǎng)絡下行速度 downlink。
console.log("網(wǎng)絡狀態(tài):" + navigator.connection.effectiveType); // 比如:4g/3g/2g
console.log("網(wǎng)絡下行速度:" + navigator.connection.downlink + "MB/S"); // 比如:10MB/S
但 navigator.connection 存在兩個問題,第一個是兼容性很差,像 Edge、Firefox、Safari 這種主流瀏覽器都不支持。

第二個是數(shù)據(jù)不準確,與一些測速器的結(jié)果有一定的偏差。基于這兩個問題,實際項目中不會采用 navigator.connection 方案。
3.2 圖片測速法
圖片測速法就是通過客戶端加載圖片,拿到圖片大小和加載時間,進而算出用戶的網(wǎng)速,其具體步驟如下:
- 加載兩張圖片,一張極小的(比如 1x1像素),一張稍大的(比如 2x2像素)。
- 記錄圖片請求開始時間,以及圖片
onLoad完成的時間。 - 計算速度:
圖片大小 / 加載時間 = 網(wǎng)速。 - 取平均值,把兩個數(shù)據(jù)取個平均值,目的是減少誤差。
小結(jié)
- 瀏覽器可以通過
navigator.onLine接口拿到瀏覽器的在線狀態(tài),并通過監(jiān)聽online和offline事件監(jiān)聽網(wǎng)絡變化。 - 由于
navigator.onLine存在一定的局限性,比如無法識別到 WiFi 連接了但無互聯(lián)網(wǎng)訪問、網(wǎng)絡延遲極高、DNS 解析失敗等情況,我們還需要通過心跳檢測方案來判斷客戶端是否能正常訪問后端 API。 navigator.connection雖然能拿到用戶的網(wǎng)絡下行速度downlink,但connection接口存在兼容性和數(shù)據(jù)不準的問題,所以我們需要用圖片測速法來更加準確地拿到用戶網(wǎng)速情況。
以上就是JavaScript檢測網(wǎng)絡連接狀態(tài)以及網(wǎng)絡測速的具體方案的詳細內(nèi)容,更多關(guān)于JavaScript檢測網(wǎng)絡連接狀態(tài)及測速的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript實例--教你實現(xiàn)撲克牌洗牌功能
我們一般都會按照順序把隨機摸過來的牌從小到大的順序在手上理整齊(記得小時候打牌兩副牌手都抓不過來),這篇隨筆就是想通過實現(xiàn)這個功能來熟悉下js中排序數(shù)組等相關(guān)知識。2014-05-05

