JavaScript 使用者代理檢測(瀏覽器型別檢測,執行平臺檢測等) 規範程式碼

Volavion發表於2020-11-03

客戶端檢測

客戶端檢測很直觀地分為: 能力檢測,怪癖檢測和使用者代理檢測

  • 能力檢測是檢驗所執行的平臺是否支援指定的函式,程式碼或者功能。建議需要才檢測的原則,可將所需要的功能進行統一檢測到初始化中,這樣可以實現能力檢測的統一管理。
  • 怪癖檢測是檢驗所執行的平臺或者引擎是否有指定的bug,該方法主要是為了避免瀏覽器的某些知名的BUG,但是一般開發者不會遇到。
  • 使用者代理檢測是檢驗JavaScript的執行程式碼的呈現引擎,瀏覽器等等而實現的方法,主要用於實現多平臺多功能。

使用者代理檢測規範程式碼

本規範程式碼參考自Nicholas C.Zakas所著的Professional JavaScript for Web Developers,可實現識別 呈現引擎種類瀏覽器種類執行平臺種類移動裝置種類遊戲系統種類 五種類別檢測。

廢話不說,放原始碼。下面我將在程式碼中加入高效的註釋來解釋程式碼的執行邏輯,以及在程式碼最後進行更為詳細的分步介紹


var client = function(){
    //rendering engines 呈現引擎的區域性變數,在此加入經典的五類引擎
    var engine = {            
        ie: 0,				
        gecko: 0,
        webkit: 0,
        khtml: 0,
        opera: 0,
        //more engine
        ver: null  
    };
    
    //browsers    瀏覽器類別的區域性變數,加入經典的六類瀏覽器
    var browser = {
        ie: 0,
        firefox: 0,
        safari: 0,
        konq: 0,
        opera: 0,
        chrome: 0,
        //specific version
        ver: null
    };
    //platform/device/OS   執行平臺的區域性變數,加入經典的三類電腦平臺,七類移動端平臺以及兩類遊戲平臺
    var system = {
        win: false,
        mac: false,
        x11: false,
        //mobile devices
        iphone: false,
        ipod: false,
        ipad: false,
        ios: false,
        android: false,
        nokiaN: false,
        winMobile: false,
        //game systems
        wii: false,
        ps: false 
    };    

    //detect rendering engines/browsers 該部分用於識別引擎和瀏覽器
    var ua = navigator.userAgent;    
    if (window.opera){
        engine.ver = browser.ver = window.opera.version();
        engine.opera = browser.opera = parseFloat(engine.ver);
    } else if (/AppleWebKit\/(\S+)/.test(ua)){
        engine.ver = RegExp["$1"];
        engine.webkit = parseFloat(engine.ver);
        
        //figure out if it's Chrome or Safari
        if (/Chrome\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.chrome = parseFloat(browser.ver);
        } else if (/Version\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.safari = parseFloat(browser.ver);
        } else {
            //approximate version
            var safariVersion = 1;
            if (engine.webkit < 100){
                safariVersion = 1;
            } else if (engine.webkit < 312){
                safariVersion = 1.2;
            } else if (engine.webkit < 412){
                safariVersion = 1.3;
            } else {
                safariVersion = 2;
            }   
            
            browser.safari = browser.ver = safariVersion;        
        }
    } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
        engine.ver = browser.ver = RegExp["$1"];
        engine.khtml = browser.konq = parseFloat(engine.ver);
    } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){    
        engine.ver = RegExp["$1"];
        engine.gecko = parseFloat(engine.ver);
        
        //determine if it's Firefox
        if (/Firefox\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.firefox = parseFloat(browser.ver);
        }
    } else if (/MSIE ([^;]+)/.test(ua)){    
        engine.ver = browser.ver = RegExp["$1"];
        engine.ie = browser.ie = parseFloat(engine.ver);
    }
    
    //detect browsers
    browser.ie = engine.ie;
    browser.opera = engine.opera;
    

    //detect platform						識別執行平臺
    var p = navigator.platform;
    system.win = p.indexOf("Win") == 0;
    system.mac = p.indexOf("Mac") == 0;
    system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);

    //detect windows operating systems
    if (system.win){
        if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){
            if (RegExp["$1"] == "NT"){
                switch(RegExp["$2"]){
                    case "5.0":
                        system.win = "2000";
                        break;
                    case "5.1":
                        system.win = "XP";
                        break;
                    case "6.0":
                        system.win = "Vista";
                        break;
                    case "6.1":
                        system.win = "7";
                        break;
                    default:
                        system.win = "NT";
                        break;                
                }                            
            } else if (RegExp["$1"] == "9x"){
                system.win = "ME";
            } else {
                system.win = RegExp["$1"];
            }
        }
    }
    
    //mobile devices
    system.iphone = ua.indexOf("iPhone") > -1;
    system.ipod = ua.indexOf("iPod") > -1;
    system.ipad = ua.indexOf("iPad") > -1;
    system.nokiaN = ua.indexOf("NokiaN") > -1;
    
    //windows mobile
    if (system.win == "CE"){
        system.winMobile = system.win;
    } else if (system.win == "Ph"){
        if(/Windows Phone OS (\d+.\d+)/.test(ua)){;
            system.win = "Phone";
            system.winMobile = parseFloat(RegExp["$1"]);
        }
    }
    
    
    //determine iOS version
    if (system.mac && ua.indexOf("Mobile") > -1){
        if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
            system.ios = parseFloat(RegExp.$1.replace("_", "."));
        } else {
            system.ios = 2;  //can't really detect - so guess
        }
    }
    
    //determine Android version
    if (/Android (\d+\.\d+)/.test(ua)){
        system.android = parseFloat(RegExp.$1);
    }
    
    //gaming systems
    system.wii = ua.indexOf("Wii") > -1;
    system.ps = /playstation/i.test(ua);
    
    //return it
    return {
        engine:     engine,
        browser:    browser,
        system:     system        
    };

}();

在程式碼執行最後,通過一個匿名函式返回結果給 client 變數。
下面是我在windows10 使用 chrome執行的結果。

型別版本
engine.webkit: 537.36engine.ver: “537.36”
browser.chrome: 85browser.ver: "85.0.4183.83"
system.win: “NT”

程式碼輸出和實現的解釋

識別呈現引擎(ie,gecko,webkit,khtml,opera)

首先使用者代理字串對於不同的瀏覽器和不同的平臺來說變化非常多,下面來列舉一些經典的使用者代理字串的例子。

引擎/瀏覽器使用者代理字串
WebKit / ChromeMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Gecko / FirfoxMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
WebKit / SafariMozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (Khtml, Kike Gecko) Safari/125.1
OperaMozilla/4.0 (compatible; MSIE 6.0); Windows NT 5.1; en) Opera 9.50

Opera

Opera經常將自己標識為其它引擎,因此我們先檢測它,並且不使用使用者代理字串,要使用windows.opera函式。

if (window.opera){ 
        engine.ver = browser.ver = window.opera.version();	
        engine.opera = browser.opera = parseFloat(engine.ver);
    }

Webkit

根據使用該引擎的瀏覽器的代理字串特點,主要去識別 AppleWebKit 字串,正規表示式組主要識別AppleWebKit和後面的版本號。ua是使用者代理字串,程式碼如下:

if (/AppleWebKit\/(\S+)/.test(ua)){
        engine.ver = RegExp["$1"];
        engine.webkit = parseFloat(engine.ver);
        }

KHTML

同理,主要識別 KHTML 字串。ua是使用者代理字串。

if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
        engine.ver = browser.ver = RegExp["$1"];
        engine.khtml = browser.konq = parseFloat(engine.ver);}

Gecko

包含Gecko的版本號位於字串“rv:”和一個閉括號之間,為了提取這個版本號,需要朝招所有不是閉括號的字元,而且還要查詢字串“Gecko/”後跟的八個數字。正規表示式為:“/rv:([^)]+)) Gecko/\d{8}/”。ua是使用者代理字串。具體程式碼為:

if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){    
        engine.ver = RegExp["$1"];
        engine.gecko = parseFloat(engine.ver)};

識別瀏覽器

由於Chrome和Safari使用同樣的WebKit引擎,因此區分這兩個瀏覽器很重要。在提取Chrome的版本號時,需要查詢字串“Chrome/”並取得該字串後面的數值。而提取Safari的版本號時,則需要查詢字串“Version/”並取得其後的數值。如果Safari為3以下的版本,則需要一些備用程式碼。具體程式碼為:

 if (/Chrome\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.chrome = parseFloat(browser.ver);
        } else if (/Version\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.safari = parseFloat(browser.ver);
        } else {
            //approximate version
            var safariVersion = 1;
            if (engine.webkit < 100){
                safariVersion = 1;
            } else if (engine.webkit < 312){
                safariVersion = 1.2;
            } else if (engine.webkit < 412){
                safariVersion = 1.3;
            } else {
                safariVersion = 2;
            }   
            browser.safari = browser.ver = safariVersion;        
        }

而對於Firefox瀏覽器,我們首先要找到“Firefox/”,然後提取該字串後面的數值。程式碼很簡單:

if (/Firefox\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.firefox = parseFloat(browser.ver);
        }

執行平臺識別

通過navigator.platform來進行檢測。具體程式碼為:

var p = navigator.platform;
    system.win = p.indexOf("Win") == 0;
    system.mac = p.indexOf("Mac") == 0;
    system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);

而平臺識別後,只有windows會返回該系統的版本資訊,但是由於不同引擎在不同作業系統中顯示不同的字串,因此需要通過如下的程式碼來通配所有的系統資訊。如下所示。
在這裡插入圖片描述

if (system.win){
        if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){
            if (RegExp["$1"] == "NT"){
                switch(RegExp["$2"]){
                    case "5.0":
                        system.win = "2000";
                        break;
                    case "5.1":
                        system.win = "XP";
                        break;
                    case "6.0":
                        system.win = "Vista";
                        break;
                    case "6.1":
                        system.win = "7";
                        break;
                    default:
                        system.win = "NT";
                        break;                
                }                            
            } else if (RegExp["$1"] == "9x"){
                system.win = "ME";
            } else {
                system.win = RegExp["$1"];
            }
        }
    }

識別移動裝置

移動裝置識別也是通過使用者代理字串檢測來實現,實現方式較為簡單。

  • iPhone, iPod 和 iPad: 通過indexOf檢測使用者代理字串是否含有該指定字串。其版本號內容較為多變。比如IOS3之前,字串只包含“CPU like Mac OS”, iphone 改成 “CPU iphone OS 3_0 like Mac OS X”, iPad中改為“CPU OS 3_2 like Mac OS X”。因此版本號必須使用正規表示式去搜尋匹配不同的規則。
` if (system.mac && ua.indexOf("Mobile") > -1){
        if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
            system.ios = parseFloat(RegExp.$1.replace("_", "."));
        } else {
            system.ios = 2;  //can't really detect - so guess
        }
    }`
  • Android作業系統識別。搜尋字串“Android”並取得緊隨其後的版本號。
    if (/Android (\d+\.\d+)/.test(ua)){
        system.android = parseFloat(RegExp.$1);
    }
  • 諾基亞N系列手機:檢測字串中的“NokianN”,並取得緊隨其後的版本號。
  • windows mobile:新老版本的windows mobile的system.win的返回值分別為 Ph和CE,因此需要正規表示式的存在。如果system.in的值是“CE”,就說明是老版本的windows Mobile,因此system.winMobile會被設定為相同的值。如果sytem.win的值是“Ph”,那麼這個裝置就可能是window phone7或更新版本。因此使用正規表示式來測試格式和版本號即可。

這個程式碼可以隨著瀏覽器的種類增多慢慢豐富起來,歡迎大家提出寶貴的意見。

相關文章