在前端 Network 還能這樣玩

這幾年手機和網絡已是大多數人生活中的必需品,其中有不少人,好比我家」超哥「,她每次到一個新的環境中通常開口都會來一句,」請問你家有 WIFI 麼,密碼是多少?「,相信不少人都有這樣的經歷。接下來,本文將介紹在前端如何實如今線或離線檢測、獲取網絡信息、獲取網絡延遲和網絡測速等內容,有興趣的小夥伴趕忙學起來。javascript

1、在線或離線檢測

在現代的瀏覽器中,能夠經過 navigator.onLine 獲取當前網絡的在線狀態,該屬性會根據用戶的網絡在線狀態返回 true 或 false。前端

navigator.onLine; // true(在線) 
navigator.onLine; // false(離線)

但在某些場景,除了須要獲取當前的網絡狀態以外,咱們更但願能監聽網絡狀態的變化,針對這個需求咱們能夠監聽 window 對象的 onlineoffline 事件,具體代碼以下:java

window.addEventListener('online', () => {
  // 網絡恢復咯,😄~~
});

window.addEventListener('offline', () => {
  // 網絡掉線咯,😢~~
});

下面咱們來看一個完整的示例,該示例會在頁面中動態顯示當前的網絡狀態:git

一、頁面加載後監聽網絡變化github

window.addEventListener('load', () => {
  // 在頁面加載後,設置正確的網絡狀態
  navigator.onLine ? showStatus(true) : showStatus(false);

  // 開始監聽網絡狀態的變化
  window.addEventListener('online', () => {
    showStatus(true);
  });

  window.addEventListener('offline', () => {
    showStatus(false);
  });
});

二、在頁面中動態顯示當前的網絡狀態npm

function showStatus(online) {
  const statusEl = document.querySelector('.network-status');

  if (online) {
    statusEl.classList.remove('warning');
    statusEl.classList.add('success');
    statusEl.innerText = `You're online! 😄`;
  } else {
    statusEl.classList.remove('success');
    statusEl.classList.add('warning');
    statusEl.innerText = `You're offline! 😢`;
  }
}
瀏覽器兼容狀況:

navigator.onLine —— https://caniuse.com/#search=navigator.onLine小程序

online event —— https://caniuse.com/#feat=mdn-api_window_online_eventsegmentfault

2、獲取網絡信息

在某些視頻網站中,當用戶在非 WIFI 狀況下點播視頻時,會展現一個友好的提醒,讓用戶確認是否在非 WIFI 的狀況下播放視頻。微信小程序

no-wifi-hint.jpeg

(圖片來源 - https://www.bilibili.com/api

要知足這個需求,咱們就須要獲取用戶當前的網絡信息。在瀏覽器中,經過 navigator.connection 能夠獲取網絡鏈接狀態 NetworkInformation 對象。

NetworkInformation 對象提供有關設備正在使用的鏈接與網絡進行通訊的信息,並提供了在鏈接類型更改時通知事件。 NetworkInformation 接口不能被是實例化, 而是經過 Navigatorconnection 屬性進行訪問,且該屬性是隻讀的。

NetworkInformation 對象中有多個只讀的屬性,好比 type 和 downlink 屬性。

一、NetworkInformation.type

返回設備正在與網絡進行通訊的鏈接類型。 它將是如下值之一:

  • bluetooth
  • cellular
  • ethernet
  • none
  • wifi
  • wimax
  • other
  • unknown

二、NetworkInformation.downlink

返回下行網絡速度,以 Mbps 爲單位。

三、NetworkInformation.downlinkMax

返回基礎鏈接技術的最大下行網絡速度,以 Mbps 爲單位。

四、NetworkInformation.effectiveType

返回鏈接的有效類型,好比 「slow-2g」,「2g」,「3g」 或 「4g」。使用最近觀察到的往返時間和下行鏈路值的組合來肯定此值。

五、NetworkInformation.rtt

表示從發送端發送數據開始,到發送端收到來自接收端的確認(接收端收到數據後便當即發送確認,不包含數據傳輸時間)總共經歷的時間。

六、NetworkInformation.saveData

若是用戶在用戶代理上設置了減小數據使用量選項,則返回 true。

若須要監聽網絡信息的變化,能夠經過 NetworkInformation.onchange 的方式來綁定監聽函數,當網絡信息發生改變時,會自動觸發 change 事件,而後執行對應的監聽函數。

介紹完上述的知識後,咱們來看個檢測是否 WIFI 環境的示例代碼:

function isWifi() {
  try {
    let wifi = true;
    const ua = window.navigator.userAgent;
    const conn = window.navigator.connection;
    // 判斷是否微信環境
    if (/MicroMessenger/.test(ua)) {
      if (ua.indexOf("WIFI") >= 0) {
        return true;
      } else {
        wifi = false;
      }
      // 判斷是否支持navigator.connection
    } else if (conn) {
      wifi = conn.type === "wifi"
    }
    return wifi;
  } catch (e) {
    return false;
  }
}

雖然經過 navigator.connection 能夠方便地獲取當前的網絡信息,不過很惋惜目前該 API 的兼容性不是很好。

navigator-connection.jpg

(圖片來源 - https://caniuse.com/ - 2020/01/23)

針對這種狀況,咱們能夠根據當前的平臺使用對應的 JS SDK 或安裝對應的網絡插件。下面咱們介紹微信、企業微信、微信小程序、釘釘和 cordova 等平臺獲取網絡信息的方式。

微信/微信小程序/企業微信

wx.getNetworkType({
  success: function (res) {
    var networkType = res.networkType; // 返回網絡類型2g,3g,4g,wifi
  }
});

釘釘

dd.device.connection.getNetworkType({
    onSuccess : function(data) {
      {
         // result值: wifi 2g 3g 4g unknown none
         // none表示離線
         result: 'wifi' 
      }
    },
    onFail : function(err) {}
});

cordova

對於 cordova 環境,能夠經過安裝 cordova-plugin-network-information 這個插件來獲取網絡信息。

function checkConnection() {
    var networkState = navigator.connection.type;
 
    var states = {};
    states[Connection.UNKNOWN]  = 'Unknown connection';
    states[Connection.ETHERNET] = 'Ethernet connection';
    states[Connection.WIFI]     = 'WiFi connection';
    states[Connection.CELL_2G]  = 'Cell 2G connection';
    states[Connection.CELL_3G]  = 'Cell 3G connection';
    states[Connection.CELL_4G]  = 'Cell 4G connection';
    states[Connection.CELL]     = 'Cell generic connection';
    states[Connection.NONE]     = 'No network connection';
 
    alert('Connection type: ' + states[networkState]);
}
 
checkConnection();
瀏覽器兼容狀況:

navigator.connection —— https://caniuse.com/#search=navigator.connection

3、獲取網絡延遲

在平常工做中,當遇到某個站點沒法訪問或網絡鏈接超時的時候,咱們常常會打開命令行,而後使用 ping 命令,ping 一下對應的站點。好比,ping 一下全球最大的同性交友平臺:

ping-github.jpg

PING (Packet Internet Groper), 因特網包探索器,用於測試網絡鏈接量的程序。Ping是工做在 TCP/IP網絡體系結構中應用層的一個服務命令, 主要是向特定的目的主機發送 ICMP(Internet Control Message Protocol 因特網報文控制協議) Echo 請求報文,測試目的站是否可達及瞭解其有關狀態。

在 Web 環境中,若是要實現 Ping 的功能,咱們可使用 Github 上 Ping.js 這個 JavaScript 庫。Ping.js 是一個小型且簡單的 JavaScript 庫,用於使用純 JavaScript 方式來獲取指定主機的網絡延遲時間。該庫的使用示例以下:

const p = new Ping();
p.ping("https://github.com", function(err, data) {
  if (err) {
    console.log("error loading resource")
    data = data + " " + err;
  }
  document.getElementById("ping-github").innerHTML = data;
});

由於 JavaScript 自己並無提供 ping 的實現,因此經過 ping.js 獲取的結果並不能保證準確性。因爲 AJAX 請求有跨域的限制,因此不能經過 AJAX 方式來實現。Ping.js 的實現方式是使用從任意主機加載 favicon.ico 圖片來確認響應時間。若 favicon.ico 圖片不存在,則會返回 error 字符串和響應時間。

ping 方法的具體實現以下:

Ping.prototype.ping = function(source, callback) {
    var self = this;
    self.wasSuccess = false;
    self.img = new Image();
    self.img.onload = onload;
    self.img.onerror = onerror;

    var timer;
    var start = new Date();

    function onload(e) {
        self.wasSuccess = true;
        pingCheck.call(self, e);
    }

    function onerror(e) {
        self.wasSuccess = false;
        pingCheck.call(self, e);
    }

    if (self.timeout) {
       timer = setTimeout(function() {
         pingCheck.call(self, undefined);
    }, self.timeout); }

    /**
     * 計算響應時間並觸發相應回調函數
     */
    function pingCheck() {
        if (timer) { clearTimeout(timer); }
        var pong = new Date() - start;

        if (typeof callback === "function") {
            if (!this.wasSuccess) {
                if (self.logError) { 
                  console.error("error loading resource"); 
                }
                return callback("error", pong);
            }
            return callback(null, pong);
        }
    }

    // 觸發圖片加載
    self.img.src = source + self.favicon + "?" + (+new Date()); 
};

對於上面的示例,執行 p.ping("https://github.com") 方法時,會發起一個 GET 請求,具體以下圖所示:

ping-github-favicon.jpg

4、網絡測速

在前端要實現網絡測速,好比計算下行帶寬,通常有如下幾種方法:

  1. 經過 AJAX 測算網速。
  2. 經過建立 Image 對象加載指定圖片來測算網速。
  3. 經過 navigator.connection.downlink 直接獲取網速。

下面咱們來重點分析一下以上幾種方案的優缺點和具體實現。

4.1 經過 AJAX 測算網速

該方案經過建立 XMLHttpRequest 對象並記錄開始時間,而後發起 AJAX 請求,當請求成功後獲取 'Content-Length' 響應頭來取得資源的大小並記錄結束時間,最後計算下行帶寬。

該方案的具體實現以下:

function getSpeedWithAjax(url) {
    return new Promise((resolve, reject) => {
        let start = null;
        let end = null;
        start = new Date().getTime();
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                end = new Date().getTime();
                const size = xhr.getResponseHeader('Content-Length') / 1024;
                const speed = size * 1000 / (end - start);
                resolve(speed);
            }
        }
        xhr.open('GET', url);
        xhr.send();
    }).catch(err => { throw err });
}

使用示例

getSpeedWithAjax('./speed.jpg')
 .then(speed => {
    console.log(speed);
});

該方案的好處是測試的文件不必定是圖片,且返回的數據量能靈活控制。很差的地方是存在跨域問題。

4.2 經過建立 Image 對象加載指定圖片來測算網速

該方案是經過建立 Image 對象並記錄開始時間,而後綁定 onload 回調函數,接着指定一個有效的圖片地址,一旦圖片加載完成就會觸發 onload 回調函數,最後在回調函數中記錄結束時間並計算下行帶寬。

該方案的具體實現以下:

function getSpeedWithImg(imgUrl, fileSize) {
    return new Promise((resolve, reject) => {
        let start = null;
        let end = null;
        let img = document.createElement('img');
        start = new Date().getTime();
        img.onload = function (e) {
            end = new Date().getTime();
            const speed = fileSize * 1000 / (end - start);
            resolve(speed);
        }
        img.src = imgUrl;
    }).catch(err => { throw err });
}

使用示例

getSpeedWithImg(
  "https://s2.ax1x.com/2019/08/13/mPJ2iq.jpg", 8.97
).then(speed => {
    console.log(speed);
});

該方案的優勢是不會存在跨域問題,很差的地方是要求文件必須是圖片且已知文件大小,文件大小不能靈活控制。使用該方案,若須要保證結果準確性,能夠考慮進行屢次測試取平均值。

4.3 經過 navigator.connection.downlink 直接獲取網速

function getSpeedWithDnlink() {
    // downlink測算網速
    const connection = window.navigator.connection;
    if (connection && connection.downlink) {
        return connection.downlink * 1024 / 8;
    }
}

使用示例

getSpeedWithDnlink();

該方案的優勢是直接調用瀏覽器提供的 API 接口,不須要提供任何參數。它的缺點是存在較大的兼容性問題,帶寬查詢不是實時的,具備分鐘級別的時間間隔。

4.4 綜合測速

最後咱們再來介紹一種綜合測速方案,即先嚐試採用 navigator.connection.downlink 測速,若當前瀏覽器不支持的話,再採用屢次 AJAX 測速並求平均值。

function getNetSpeed(url, times) {
    // downlink測算網速
    const connection = window.navigator.connection;
    if (connection && connection.downlink) {
        return connection.downlink * 1024 / 8;
    }
    // 屢次測速求平均值
    const arr = [];
    for (let i = 0; i < times; i++) {
        arr.push(getSpeedWithAjax(url));
    }
    return Promise.all(arr).then(speeds => {
        let sum = 0;
        speeds.forEach(speed => {
            sum += speed;
        });
        return sum / times;
    })
}
備註:本章節的示例代碼來源於 Github 上 network-speed-test 這個開源項目。

5、參考資源

本人的全棧修仙之路訂閱號,會按期分享 Angular、TypeScript、Node.js/Java 、Spring 相關文章,歡迎感興趣的小夥伴訂閱哈!

full-stack-logo

相關文章
相關標籤/搜索