客戶端檢測

一、能力檢測

  • 基本概念html

    • 定義:最經常使用也最爲人們普遍接受的客戶端檢測形式是能力檢測(又稱特性檢測)android

    • 目標:不是識別特定的瀏覽器,而是識別瀏覽器的能力,,基本模式以下:ios

      if (object.propertyInQuestion){
       //使用 object.propertyInQuestion
      }
    • 舉例:web

      function getElement(id){
          if (document.getElementById){
              return document.getElementById(id);
          } else if (document.all){
              return document.all[id]; 
          } else {
              throw new Error("No way to retrieve element!");
          }
      }
    • 要理解能力檢測,首先必須理解兩個重要的概念:chrome

      • 先檢測達成目的的最經常使用的特性
      • 必須測試實際要用到的特性
      function getWindowWidth(){
          if (document.all){ //假設是 IE
              return document.documentElement.clientWidth; //錯誤的用法!!!
          } else {
              return window.innerWidth;
          }
      }
  • 更可靠的能力檢測windows

    • 錯誤的能力檢測:瀏覽器

      //不要這樣作!這不是能力檢測——只檢測了是否存在相應的方法
      function isSortable(object){
          return !!object.sort;//任何包含 sort屬性的對象也會返回 true
      } 
      var result = isSortable({ sort: true });
    • 在可能的狀況下,要儘可能使用 typeof進行能力檢測服務器

      //這樣更好:檢查 sort 是否是函數
      function isSortable(object){
          return typeof object.sort == "function";
      }
    • 關於document.createElement()的檢測iphone

      • 大多數瀏覽器在檢測到document.createElement()存在時,都會返回 trueide

      • 在 IE8 及以前版本中,返回 false,由於 typeof document.createElement 返回的是"object",而不是"function";document.createElement()函數確實是一個 COM 對象,不是DOM 對象

        //在 IE8 及以前版本中不行
        function hasCreateElement(){
          return typeof document.createElement == "function";
        }
      • IE9 糾正了這個問題,對全部 DOM 方法都返回"function"

    • 關於ActiveX 對象(只有 IE 支持)的檢測

      • 不使用typeof測試某個屬性會致使錯誤

        //在 IE 中會致使錯誤
        var xhr = new ActiveXObject("Microsoft.XMLHttp");
        if (xhr.open){ //這裏會發生錯誤
          //執行操做
        }
      • 使用 typeof操做符:注意但 IE對 typeof xhr.open會返回"unknown"

        //做者:Peter Michaux
        function isHostMethod(object, property) {
          var t = typeof object[property];
          return t=='function' ||(!!(t=='object' && object[property])) ||
                   t=='unknown';
        } 
        result = isHostMethod(xhr, "open"); //true
        result = isHostMethod(xhr, "foo"); //false
  • 能力檢測和瀏覽器檢測

    • 檢測某個或某幾個特性並不可以肯定瀏覽器

      //錯誤!還不夠具體
      var isFirefox = !!(navigator.vendor && navigator.vendorSub);
      //錯誤!假設過頭了
      var isIE = !!(document.all && document.uniqueID);
    • 根據瀏覽器不一樣將能力組合起來是更可取的方式

      //肯定瀏覽器是否支持 Netscape 風格的插件
      var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
      //肯定瀏覽器是否具備 DOM1 級規定的能力
      var hasDOM1 = !!(document.getElementById && document.createElement &&
       document.getElementsByTagName);
    • 在實際開發中,應該將能力檢測做爲肯定下一步解決方案的依據,而不是用它來判斷用戶使用的是什麼瀏覽器

二、怪癖檢測

  • 概念:與能力檢測相似,目標是識別瀏覽器的特殊行爲;但與能力檢測確認瀏覽器支持什麼能力不一樣,怪癖檢測是想要知道瀏覽器存在什麼缺陷(「怪癖」也就是 bug)這一般須要運行一小段代碼,以肯定某一特性不能正常工做

  • 舉例:

    • IE8 及更早版本中存在一個 bug,即若是某個實例屬性與[[Enumerable]]標記爲 false 的某個原型屬性同名,那麼該實例屬性將不會出如今fon-in 循環當中

      var hasDontEnumQuirk = function(){
          var o = { toString : function(){} };
          for (var prop in o){ 
              if (prop == "toString"){return false;}
          }
          return true;
      }();
    • Safari 3 之前版本會枚舉被隱藏的屬性

      var hasEnumShadowsQuirk = function(){
          var o = { toString : function(){} };
          var count = 0;
          for (var prop in o){
              if (prop == "toString"){
                  count++;
              }
          }
          return (count > 1);
      }();

三、用戶代理檢測

  • 概念:
    • 用戶代理檢測:經過檢測用戶代理字符串來肯定實際使用的瀏覽器;在每一次 HTTP 請求過程當中,用戶代理字符串是做爲響應首部發送的,並且該字符串能夠經過 JavaScript 的 navigator.userAgent屬性訪問;在服務器端,經過檢測用戶代理字符串來肯定用戶使用的瀏覽器是一種經常使用並且廣爲接受的作法,而在客戶端,用戶代理檢測通常被看成一種萬不得已才用的作法,其優先級排在能力檢測和(或)怪癖檢測以後
    • 電子欺騙:就是指瀏覽器經過在本身的用戶代理字符串加入一些錯誤或誤導性信息,來達到欺騙服務器的目的
  • 用戶代理字符串的歷史

    • 早期的瀏覽器:Mozilla/版本號 [語言] (平臺; 加密類型)
    • Netscape Navigator 3 :Mozilla/版本號 (平臺; 加密類型 [; 操做系統或 CPU 說明])
    • Internet Explorer 3 :Mozilla/2.0 (compatible; MSIE 版本號; 操做系統)
    • Netscape Communicator 4:Mozilla/版本號 (平臺; 加密類型 [; 操做系統或 CPU 說明])
    • IE4~IE8:Mozilla/4.0 (compatible; MSIE 版本號; 操做系統; Trident/Trident 版本號)
    • Gecko:Mozilla/Mozilla 版本號 (平臺; 加密類型; 操做系統或 CPU; 語言; 預先發行版本) Gecko/Gecko 版本號 應用程序或產品/應用程序或產品版本號
    • WebKit :Mozilla/5.0 (平臺; 加密類型; 操做系統或 CPU; 語言) AppleWebKit/AppleWebKit 版本號 (KHTML, like Gecko) Safari/Safari 版本號
    • Konqueror:Mozilla/5.0 (compatible; Konqueror/ 版本號; 操做系統或 CPU) KHTML/ KHTML 版本號 (like Gecko)
    • Chrome :Mozilla/5.0 ( 平臺; 加密類型; 操做系統或 CPU; 語言) AppleWebKit/AppleWebKit 版本號 (KHTML,like Gecko) Chrome/ Chrome 版本號 Safari/ Safari 版本
    • Opera :Opera/ 版本號 (操做系統或 CPU; 加密類型; 語言)
    • iOS 和 Android :Mozilla/5.0 (平臺; 加密類型; 操做系統或 CPU like Mac OS X; 語言) AppleWebKit/AppleWebKit 版本號 (KHTML, like Gecko) Version/瀏覽器版本號 Mobile/移動版本號 Safari/Safari 版本號
  • 用戶代理字符串檢測技術

    //用戶代理字符串檢測腳本,包括檢測呈現引擎、平臺、Windows 操做系統、移動設備和遊戲系統
    var client = function(){
      //呈現引擎
      var engine = {
          ie: 0,
          gecko: 0,
          webkit: 0,
          khtml: 0,
          opera: 0,
          //完整的版本號
          ver: null
      };
      //瀏覽器
      var browser = {
          //主要瀏覽器
          ie: 0,
          firefox: 0,
          safari: 0,
          konq: 0,
          opera: 0,
            chrome: 0,
          //具體的版本號
          ver: null
        };
        //平臺、設備和操做系統
      var system = {
          win: false,
          mac: false,
          x11: false,
          //移動設備
          iphone: false,
          ipod: false,
          ipad: false,
          ios: false,
          android: false,
          nokiaN: false,
          winMobile: false,
          //遊戲系統
          wii: false,
          ps: false
      }; 
        //檢測呈現引擎和瀏覽器
      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);
          //肯定是 Chrome 仍是 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 {
              //近似地肯定版本號
              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);
          //肯定是否是 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);
      } 
        //檢測瀏覽器
      browser.ie = engine.ie;
      browser.opera = engine.opera; 
      //檢測平臺
      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"];
              }
          }
      }
        //移動設備
        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"]);
          }
      }
    
      //檢測 iOS 版本
      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; //不能真正檢測出來,因此只能猜想
          }
      }
      //檢測 Android 版本
      if (/Android (\d+\.\d+)/.test(ua)){
          system.android = parseFloat(RegExp.$1);
      }
      //遊戲系統
      system.wii = ua.indexOf("Wii") > -1;
       system.ps = /playstation/i.test(ua);
      //返回這些對象
      return {
          engine: engine,
          browser: browser,
          system: system
      };
    }();
  • 使用方法:用戶代理檢測是客戶端檢測的最後一個選擇。只要可能,都應該優先採用能力檢測和怪癖檢測。用戶代理檢測通常適用於下列情形:

    • 不能直接準確地使用能力檢測或怪癖檢測。例如,某些瀏覽器實現了爲未來功能預留的存根(stub)函數。在這種狀況下,僅測試相應的函數是否存在還得不到足夠的信息

    • 同一款瀏覽器在不一樣平臺下具有不一樣的能力。這時候,可能就有必要肯定瀏覽器位於哪一個平臺下
    • 爲了跟蹤分析等目的須要知道確切的瀏覽器

四、總結

  • 客戶端檢測是 JavaScript 開發中最具爭議的一個話題。因爲瀏覽器間存在差異,一般須要根據不一樣瀏覽器的能力分別編寫不一樣的代碼。有很多客戶端檢測方法,但下列是最常用的:
    • 能力檢測:在編寫代碼以前先檢測特定瀏覽器的能力。例如,腳本在調用某個函數以前,可能要先檢測該函數是否存在。這種檢測方法將開發人員從考慮具體的瀏覽器類型和版本中解放出來,讓他們把注意力集中到相應的能力是否存在上。能力檢測沒法精確地檢測特定的瀏覽器和版本
    • 怪癖檢測:怪癖其實是瀏覽器實現中存在的 bug,例如早期的 WebKit 中就存在一個怪癖,即它會在 for-in 循環中返回被隱藏的屬性。怪癖檢測一般涉及到運行一小段代碼,而後肯定瀏覽器是否存在某個怪癖。因爲怪癖檢測與能力檢測相比效率更低,所以應該只在某個怪癖會干擾腳本運行的狀況下使用。怪癖檢測沒法精確地檢測特定的瀏覽器和版本
    • 用戶代理檢測:經過檢測用戶代理字符串來識別瀏覽器。用戶代理字符串中包含大量與瀏覽器有關的信息,包括瀏覽器、平臺、操做系統及瀏覽器版本。用戶代理字符串有過一段至關長的發展歷史,在此期間,瀏覽器提供商試圖經過在用戶代理字符串中添加一些欺騙性信息,欺騙網站相信本身的瀏覽器是另一種瀏覽器。用戶代理檢測須要特殊的技巧,特別是要注意 Opera會隱瞞其用戶代理字符串的狀況。即使如此,經過用戶代理字符串仍然可以檢測出瀏覽器所用的呈現引擎以及所在的平臺,包括移動設備和遊戲系統
  • 在決定使用哪一種客戶端檢測方法時,通常應優先考慮使用能力檢測。怪癖檢測是肯定應該如何處理代碼的第二選擇。而用戶代理檢測則是客戶端檢測的最後一種方案,由於這種方法對用戶代理字符串具備很強的依賴性
相關文章
相關標籤/搜索