美文网首页
客户端检测

客户端检测

作者: 了凡和纤风 | 来源:发表于2019-07-22 18:01 被阅读0次

    本文主要简单介绍客户端检测的三种方式:能力检测怪癖检测用户代理检测

    能力检测

    最常用、广泛的客户端检测形式 是 能力检测(又称特性检测)。额能力检测的目标不是识别浏览器的能力。而实采用这种方式确定浏览器支持特定的能力。

    if (object.propertyInQuestion) {
      // 使用 object.propertyInQuestion
    }
    

    举例来说,IE5之前不支持document.getElementById()这个DOM方法,可以使用非标准的document.all属性

    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!')
      }
    }
    

    要理解能力检测,有两个很重要的概念。第一个概念就是先检测达成目的的最常用特性;第二个概念是必须测试实际要用到的特性

    function getWindowWidth() {
      if (document.all) { // 假设是IE
        return document.documentElement.clientWidth // 错误用法
      } else {
        return window.innerWidth
      }
    }
    

    上面这是一个错误的能力检测例子。document.all 存在也不一定表示浏览器就是IE。实际上,也有可能是Opera。

    更可靠的能力检测

    能力检测对于想知道某个特性是否会按照适当方式行事(而不仅仅是某个特性存在)非常有用。

    function isSortable(object) {
      return !!object.sort
    }
    

    上面的代码,原意是像测试一个对象是否支持 sort方法。但是以上的代码并不是能力检测——只是确定一个对象是否存在对应属性或方法。如下:

    var res = isSortable({sort: true})
    console.log(res) // true
    

    在这种测试中,要尽量 使用到 typeof 来检测类型

    // 这样更好,检测sort是不是函数
    function isSortable(object) {
      return typeof object.sort === 'function'
    }
    

    但 typeof 并不是所有情况下能都满足我们的需求,比如在老版本的IE中。

    // 检测 document.createElement()
    function hasCreateElement() {
      return typeof document.createElement == 'function'
    }
    

    在IE8 及 之前版本中, typeof document.createElement 返回的是 ‘object’。DOM对象是宿主对象,IE及更早版本的宿主对象是通过COM而非JScript实现的。IE9纠正了这个问题,对DOM方法都返回‘function’。
    关于typeof的行为不标准,IE中还可以举出例子来。ActiveX对象(只有IE支持)于其他对象的行为差异大。不使用typeof测试某个属性就会导致错误。

    // 在 IE 中会导致错误
    var xhr = new ActiveXObject("Microsoft.XMLHttp")
    if (xhr.open) { // 这里会发生错误
      // todo
    }
    

    像上面这样直接把函数作为属性访问会导致JavaScript错误。使用typeof 操作符会更靠谱一点,但IE对 typeof xhr.open 会返回 "unknown"

    function isHostMethod(object, property) {
      var t = typeof object[property]
      return t = 'function' || !!(t == 'object' && object[property]) || t == 'unknown'
    }
    
    var result = isHostMethod(xhr, 'open')
    

    isHostMethod() 方法还是比较全面的,因为它考虑到了浏览器的怪异行为。不过也要注意,宿主对象没有义务保持目前的实现方式不变,也不一样会魔法已有宿主对象的行为。

    能力检测,不是浏览器检测

    检测某个或几个特性斌不能确定浏览器。下面这种“浏览器检测”代码就是错误地依赖能力检测的典型实例。

    // 错误!不够具体
    var isFirefox = !!(navigator.vendor && navigator.bendorSub)
    
    // 错误!假设过头了
    var isIE = !!(document.all && document.uniqueID)
    

    确实可以通过navigator.vendor 和 navigator.vendorSub 来确定Firefox。但是,Safari也实现了相同的属性;document.all 和 document.uniqueID检测IE,就相当于假设IE将来版本中任然会继续存在这两个属性,同时还假设其他浏览器都不会实现这两个属性。实际上,根基于浏览器不同将能力组合起来是更可取的方式。

    // 确定浏览器是否支持 Netscape 风格的插件。
    var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length)
    
    // 去顶浏览器是否具有DOM1 级规定的能力。
    var hasDOM1 = !!(document.getElementById && document.createElement && document.getElementsByTagName)
    

    怪癖检测

    怪癖检测(quirks detection)的目标是识别浏览器的特殊欣慰。怪癖检测是想知道浏览器存在上面弊端(也就是bug)。这通常都需要运行一下段代码,已确定某一特性能否正常工作。

    例如:IE8中存在这么一个Bug,即如果某个实例属性 于 [[ Enumerable ]]标记为 false的原型属性同名,那么该实例属性不会出现在 for - in 循环当中。

    // 检测如上描述的这种怪癖
    var hasDontEnumQuirk = function () {
      var o = { toString: function() {} }
      for (var prop in o) {
        if (prop == 'toString') {
          return false
        }
      }
    return true
    }()
    console.log(hasDontEnumQuirk )
    

    同时在 Safari 3 以前版本会枚举被隐藏的属性。

    var hasEnumShadowsQuirk = function() {
      var o = { toString: function() {} }
      var count = 0
      for(var prop in o) {
        if (prop === 'toString') {
          count++
        }
      }
      // 是否多次枚举
      return (count > 1)
    }
    

    一般来说, “怪癖”都是个别浏览器所独有的,而且通常被归为bug。由于检测“怪癖”涉及运行代码,因此建议仅检测那些对你有直接影响的“怪癖”,而且最好在脚本一开始的时候就执行此类检测,以便尽早解决问题。

    用户代理检测

    第三种,也是争议最大的一种客端检测技术叫做用户代理检测。通过检测用户代理字符串来确定实际使用的浏览器。HTTP请求过程中,用户代理字符串作为响应首部发送的,可以通过navigator.userAgent属性进行访问。
    在服务器端,通过检测用户代理字符串来确定用户使用的浏览器是一种常用而且广为接受的做法。
    而在客户端,用户代理检测一般被当作一种万不得已才用的做法,其优先级排在能力检测和(或)怪癖检测之后。

    提到于用户代理有关的争议,就不得不提到电子欺骗(spoofing)。即浏览器通过在自己的用户代理字符串加入一些错误或误导性的信息,来达到欺骗服务器的目的。要弄清这个问题的来龙去脉,必须从Web问世初期用户代理字符串的发展讲起

    用户代理字符串检测技术

    一般情况下,知道呈现引擎和最低的版本就足以决定正确的操作方法。

    // 推荐写法
    if( isVer >= 6) {
      // todo
    }
    
    // 不推荐 --
    if (isIE6 || isIE7) {
      // todo
    }
    
    1.识别呈现引擎

    确切知道浏览器的名字和版本不如知道它使用的是上面呈现引擎。Firefox、Camino、Netscape都使用相同版本的Gecko,那它们一定支持相同的特性。类似的,只要更Safari3使用的是同一个版本的WebKit,那么该浏览器也就跟Safari3具有同样的功能。
    我们编写的脚本主要检测五大呈现引擎:IE、Geeko、WebKit、KHTML、Opera

    我们使用增强模块来封装检测脚本。

    var client = function() {
      var engine = {
        ie: 0,
      gecko: 0,
      webkit: 0,
      khtml: 0,
      opera: 0,
      
      // 具体版本号
      ver: null
      }
    
      return {
        engine: engine
      }
    }()
    

    每个引擎都对应一个属性,属性的默认值为0,。如果检测到了某个呈现引擎,那么就以浮点数形式将该引擎的版本号写入相应的属性。而呈现引擎的完整版本,则以字符串的形式写入 ver 中
    如上这样属性可以支持以下形式

    if (client.engine.ie) { // 如果是IE,client.ie的值应该大于0
      // todo
    } else if (client.engine.gecko >= 1.5 ){ // 根据版本需求
      if (client.engine.ver == '1.8.1') { // 针对特定版本
        // todo
      }
    }
    

    因为每个属性保存的是一个浮点数值,因此有可能丢失一些信息。例如,将字符串'1.8.1'转换为浮点数之后(parseFloat())后会得到1.8。不过必要的时候可以检测 ver 属性来得到具体的版本信息。

    要正确地识别引擎,关键是检测顺序要正确。由于用户代理字符串存在诸多不一致的地方,如果检测顺序不对,很可能会导致检测结果不正确。

    1. 识别 Opera
    第一个应该识别Opera,因为其用户代理字符串可以完全模仿其他浏览器
    要识别 Opera 浏览器,必须检测 window.opera 对象。Opera5及以上版本都包含这个对象,用于保存与浏览器相关的标识信息以及与浏览器直接交互。
    Opera 7.6及更高版本中,调用 version()方法返回一个表示浏览器的字符串,这也是确定Opera的最佳方式。
    更早版本的Opera可以直接查询代理字符串,因为那些版本还不支持隐藏身份。

    if (window.opera) { // opera 5+
      engine.ver = window.opera.version() // 获取版本号
      engine.opera = parseFloat(engine.ver) // 将版本号转换为浮点数
    }  
    

    因为 Opera的目前版本已经非常高了,所有不太可能还有人会使用较低版本。
    2. 识别 WebKit
    放在第二位进行检测的是呈现引擎是 WebKit,其用户代理字符串中包含“Gecko”和 “KHTML”这两个字符串,所有如果首先检测它们,容易得出错误的结论。
    WebKit的用户代理字符串中“AppleWebKit”是独一无二的,因此检测这个府丰城最合适。

    // 例如Chrome的代理字符串:
    // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
    var ua = navigator.userAgent
    var reg = /AppleWebKit\/(\S+)/
    if (reg.test(ua)) { // 
      engine.ver = RegExp["$1"] // 获取最近捕获到的对象
      engine.webkit = parseFloat(engine.ver) // 转换浮点数
    } 
    

    Webkit 与 Safari 版本的详细对应情况如下表所示。

    Safari版本号 最低限度的WebKit版本号
    1.0至1.02 85.7
    1.0.3 85.8.2
    1.1至1.1.1 100
    1.2.2 125.2
    1.2.3 125.4
    1.2.4 125.4
    1.2.4 125.5.4
    2.0.3 417.9
    2.0.4 418.8
    3.0.4 523.10
    3.1 525

    有时候,Safari版本并不会与WebKit版本严格地一一对应,也有可能会存在某些小版本上的差异。这个表中只是列出了最可能的WebKit版本,但不保证精确。

    3. 识别 KHTML
    KHTML的用户代理字符串中也包含“Gecko”,因此在排除KHTML之前,我们无法准确检测基于Gecko的浏览器。KHTML的版本号与的WebKit的版本号在用户代理字符串中的格式差不多,因此可以使用类似的正则表达式。此外,由于Konqueror3.1 及更早版本中不包含KHTML版本,故而就要使用Konqeror的版本代替。

    var ua = navigator.userAgent
    // Mozilla/5.0 (compatible; Konqueror/3.5; SunOS) KHTML/3.5.0 (like Gecko)
    var khtmlReg = /KHTML\/(\S+)/
    var konReg = /Konqueror\/([^;]+)/ // 取反操作符
    if (khtmlReg.test(ua) || konReg.test(ua)) {
      engine.ver = RegExp["$1"]
      engine.khtml = parsetFloat(engine.ver)
    }
    

    Konqueror后跟一个斜杠,再后到不包含;的一个或多个字符
    4. 识别 Gecko
    在排除WebKitKHTML之后,就可以准确地检测Gecko了。但是,在用户代理字符串中,Gecko的版本号不会出现在字符串“Gecko”后面,而是会出现在字符串“rv”后面。

    // Windows XP 下的Firefox 2.0.0.11:
    // Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
    var ua = navigator.userAgent
    var reg = /rv:([^\)+])\) Gecko\/\d{8}/
    if (reg.test(ua)) {
      enigne.ver = RegExp["$1"]
      enigne.gecko = parseFloat[engine.ver]
    }
    

    上面这个正则看起来略微复杂,搜先匹配到 rv: 然后捕获 ) 之前的字符,随后继续匹配 ) Gecko/
    Gecko与Firefox版本号的对应关系如下表所示。

    Firefox版本号 最低限度的Gecko版本号
    1.0 1.7.5
    1.5 1.8.0
    2.0 1.8.1
    3.0 1.9.0
    3.5 1.9.1
    3.6 1.9.2
    4.0 2.0.0

    跟Safari跟WebKit一样,Firefox与Gecko的版本号也不一定严格对应
    5. 识别 IE
    最后一个检测的呈现引擎是 IE。 IE的版本号以便位于字符串“MSIE”的后面,一个分号的前面,因此相应的正则表达式非常简单。

    var ua = navigator.userAgent
    // Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
    var reg = /MSIE ([^;]+)/
    if(res.test(ua)) {
      engine.ver = RegExp["$1"]
      engine.ie = parseFloat(engine.ver)
    }
    

    IE通常会保证以标准浮点数值形式给出其版本号,但有时候也不一定。因此,取反的字符类[^;]可以确保取得多个小数点以及任何可能的字符。

    识别浏览器

    大多数情况下,识别了浏览器的呈现引擎就足以我们采取正确的操作提供依据了。可是,只有呈现引擎不能说明存在所需的JavaScript功能。例如:Safari和Chrome都是他WebKit作为呈现引擎,但它们的JavaScript引擎却不相同。
    对此,在 client对象中添加如下代码

    var browser = {
      // 浏览器
      ie: 0,
      firefox: 0,
      safari: 0,
      konq: 0,
      opera: 0,
      chrome: 0,
      
      // 具体版本
      ver: null
    }
    
      return {
        browser: browser
      }
    

    同样,属性中保存的是浮点数值形式的版本号。ver属性中保存的字符串形式的完整版本号。由于大多数浏览器与其呈现引擎密切相关,所以下面示例中检测浏览器的代码与检测呈现引擎的代码是混合在一起的。

    1. Chrome 和 Safari
    这两个浏览器目前是基于苹果的WebKit渲染引擎

    var ua = navigator.userAgent
    // WebKit呈现引擎
    if (/AppleWebKit\/(\S+)/.test(ua)) {
      // todo 呈现引擎
    
      // 浏览器操作
      if (/Chrome\/(S+)/.test(ua)) { // 是否是 Chrome 浏览器
        browser.ver = RegExp["$1"]
        browser.chrome = parseFloat(browser.ver)
      } else if( /Version\/(/S+)/.test(ua)) { // 是否位 Safari
    // 3.0以后增加 Version记号
    // Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) ——AppleWebkit/622.15.5 (KHTML, like Gecko) Version/3.0.3 Safari/522.15.5
        browser.ver = RegExp["$1"]
        browser.safari = parseFloat(browser.ver)
      } else { // Safari 低版本
        // 近似的确定版本号
        var safariVersion = 1
        if (engine.webkit < 100) {
          safariVersion = 1
        } else if (engine.webkit < 312) {
          safariVersion = 1.2
        } else if (engine.webkit < 421) {
          safariVersion = 1.3
        } else {
          safariVersion = 2
        }
      // 因为是估算的近似版本,所以ver不会存储详细版本号
      browser.safari = browser.ver = safariVersion
      } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) { // KHTML引擎,konqueror浏览器
        engine.ver = browser.ver = RegExp["$1"]
        engine.khtml = browser.konq = parseFloat(engine.ver)
      } else if (/rv:([^\)]+) Gecko\/\d{8}/.test(ua)) { // Gecko引擎,Firefox
        // todo 呈现引擎
    
        // "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0"
        if (/Firefox\/(\S+)/.test(ua)) { // 检测 Firefox浏览器
          browser.ver = RegExp["$1"]
          browser.firefox = parserFloat(browser.ver)
        }
      } else if (/MSIE ([^;]+)/.test(ua)) { // IE引擎
        engine.ver = browser.ver = RegExp["$1"]
        engine.ie = browser.ie = parseFloat(engine.ver)
      }
    }
    

    对于 Opera 和 IE 而言,browser 对象中的值等于 engine对象中的值。对于 Konqueror 而言,browser.konqbrowser.ver属性分别等于 engine.KHTMLengine.ver属性。

    检测Chrome和Safari,在检测Chrome时需要提取字符串Chrome/并取得后面的版本号。而在Safari时,需要查找字符串Version并取得后年的值,但这种方式仅适用与Safari3及以上版本,因此需要一些备用代码,用WebKit的版本号近似的映射出Safari的低版本号

    检测Firefox时,需要找到字符串Firefox/,并提取后面的版本号。

    根据以上函数中的代码,我们就可以编写以下逻辑

    if(client.engine.webkit) { // webkit
      if (client.browser.chrome) { // Chrome 浏览器
        // todo
      } else if (client.browser.safari) { // Safari 浏览器
        // todo
      }
    } else if (client.engine.gecko) { // Gecko
      if ( client.browser.firefox ) { // Firefox
        // todo
      } else { // 其他Gecko浏览器的代码
        // todo
      }
    }
    
    识别平台

    很多时候,只要直到呈现引擎就注意编写出适当的代码了。在某些条件下,平台可能是必须关注的问题如Windows、Mac、Unix(包括各种Linux)

    为此需要继续添加代码,用于存储平台信息

    
    var system = {
      win: false,
      mac: false,
      x11: false
    }
    
    return {
      system: system
    }
    

    win属性表示为windows平台,mac表示Mac,x11不表示Unix。
    对着三个浏览器而言,浏览器一般只报告Windows的版本。为此,新变量system的每个属性最初都保存着布尔值,而不是呈现引擎那样保存数值。

    在检测平台是,检测navigator.platform 要比检测用户代理字符串更简单,后者在不同浏览器中会给出不同的平台信息。其中可能包括Win32、Win64、MacPPC、MacIntel、X11、Linux 1686,这些值在不同的浏览器中都是一致的。检测平台的代码非常直观。

    var p = navigator.platform
    system.win = p.indexOf("Win") == 0
    system.mac = p.indexOf("Mac") == 0
    system.x11 = (p.indexOf("X11") == 0 || p.indexOf("Linux") == 0)
    
    识别 Windows 操作系统

    在Windows平台下,还可以从用户代理字符串中进一步取得具体的操作系统信息。windows中有两种版本(分别针对家庭版和商业版)
    家庭用户的版本分别是Windows95、Windows98、WIndows ME
    商业用户的版本则一直叫做 Windows NT,最后由于市场原因改名为Windows 2000。这两个产品线后来又合并成一个由Windows NT发展而来的公共的代码基,代表产品就是Windows XP。随后,微软在Windows XP基础上又构建了 Windows Vista

    下表列出不同浏览器在表示不同Windows操作系统时给出的字符串

    Widnows版本 IE4+ Gecko Opera < 7 Opera 7+ WebKit
    95 "Windows 95" "Win95" "Windows 95" "Windows 95" n/a
    98 "Windows 98" "Win98" "Windows 98" "Windows 98" n/a
    NT 4.0 "Windows NT" "WinNT4.0" "Windows NT 4.0" "Windows NT 4.0" n/a
    2000 "Windows NT 5.0" "Windows NT 5.0" "Windows 2000" "Windows NT 5.0" n/a
    ME "Win 9x 4.90" "Win 9x 4.90" "Windows ME" "Win 9x 4.90" n/a
    XP "Windows NT 5.1" "Windows NT 5.1" "Windows XP" "Windows NT 5.1" "Windows NT 5.1"
    Vista "Windows NT 6.0" "WIndows BT 6.0" n/a "Windows NT 6.0" "Windows NT 6.0"
    7 "Windows NT 6.1" "Windows NT 6.1" n/a "Windows NT 6.1" "Windows NT 6.1"

    为了检测不同的Windows操作系统,需要用到正则,由于使用OPera 7之前版本的用户已经不多了,因此我们可以忽略这部分浏览器。

    首先匹配 Windows 95 和 Windows 98对这两个字符串,只有Gecko与其他浏览器不同,即没有“dows”,而且“win”与版本号之间没有空格。要匹配这个模式,可使用下面的简单正则

    /Win(?:dows )?([^do]{2})/
    

    上面这个正则表达式,第一个?表示非获取匹配,匹配到的内容不会进行捕获;第二个?表示前面的内容1次或0次。这个版本可能是任何来个字符编码(例如95、98、9x、NT、ME、XP),因此需要两个非空空格。


    Gecko在表示Windows NT时会在末尾添加"4.0",于其查找实际的字符串,不如像下面这样查找小数值更合适。

    /Win(?:dows )?([^do]{2})(\d+\.+\d)?/
    

    这样,正则表达式中就包含了第二个捕获组,用于取得NT的版本号,由于该版本号对于Windows95 和 Windows 98而言时不存在的,所以必须设置为可选。


    上面这个模式于Opera表示Windows NTT的字符串之间的唯一的区别就是,NT 于 4.0之间有无空格。

    /Win(?:dows)?([^do{2}])\s?(\d+\.\d+)?/
    

    这个正则表达式可以成功匹配 Windows ME、Windows XP 和 Windows Vista的字符串了。具体来说,第一个捕获组将会匹配 95、98、9x、NT、ME、XP第二个捕获组则只针对WIndows ME及所有WIndows NT的变体。 这个信息作为吉他的操作系统信息保存在system.win属性中。

    var ua = navigator.userAgent
    if (system.win) { // windows 平台下
      if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) {
        if (RegExp["$1"] == "NT") {
          // 则有可能时 NT4.0 XP Vista 7
          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') { // ME
          system.win = "ME"
        } else { 
          system.win = RegExp["$1"]
        }
      }
    }
    

    根据如上代码可以变下如下测试代码

    if (client.system.win) { // windows 平台
      if (client.system.win == 'XP') { // XP系统
        // todo
      } else if (client.system == 'Vista') { // Vista 系统
        // todo
      }
    }
    
    识别移动端设备

    首先继续在对象中添加如下属性

    var system = {
      win: false,
      mac: false,
      x11: false
    
      // 移动设备
      iphone: false,
      ipod: false,
      ipad: false,
      ios: false,
      android: false,
      nokiaN: false,
      winMobile: false
    }
    
    return {
      system: system
    }
    

    检测IOS
    通过检点的检测字符串"iphone"、"ipod"、"ipad",就可以分别设置响相应的属性值了。

    system.iphone = ua.indexOf("iPhone") > -1
    system.ipod = ua.indexOf("ipod") > -1
    system.ipad = ua.indexOf("ipad") > -1
    

    除了知道IOS设置,最好还能知道IOS的版本号。在IOS3之前,用户代理字符串中质保函"CPU like Mac OS"后来iPhone又改成"CPU iPhone OS 3 0 like Mac OS X"iPad中文又改成"CPU OS 3_2 like Mac OS X"。检测IOS需要正则来反映这些变化

    // 检测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
    通过搜索字符串“Android”并取得跟紧其后的版本号。

    // 检测 Android 版本
    if (/Android (\d+\.\d+)/.test(ua)) {
      system.android = parseFloat(RegExp.$1)
    }
    

    由于所有版本的Android都有版本值,所以这个正则表达式可以精确地检测所有版本


    洛基亚
    洛基亚N系列使用的也是WebKit,但是太老了,估计也没人用,这里顺带提一下。


    Windows Mobile
    日常使用的也比较少
    Windows Mobile(也成为 Windows CE),用于 Pocket PC 和 SmartPhone中。从技术上来说这些也属于 Windows 平台,因此 Windows 平台和操作系统都会返回正确的值。对于Windows Mobile 5.0及以前版本,这两种设置的用户代理字符串非常相似。
    Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC, 240x320)
    Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)

    当Windows操作系统检测脚本检测这两个字符串时,system.win 将被设置为"CE",因此在检测Windows Mobile 时可以使用这个值。

    system.winMobile = (system.win == "CE")
    

    在Windows Phone7 的用户代理字符串稍微有改进,基本格式如下
    Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0) Asus;Galaxy6

    其中,Windows操作符的标识符与以往完全不同,因此在这个用户代理中 client.system.win等于"ph".

    // windows mobile
    if(system.win == 'CE') { // 老版本 Windows Mobile
      system.winMobile = system.win
    } else if (system.win == 'ph') { // Windwos Phone 7 或更新版本
      if (/Windows phone OS (\d+.\d+)/.test(ua)) {
        system.win = "phone"
        system.winMobile = parseFloat(RegExp["$1"])
      }
    }
    
    识别游戏系统

    除了移动设之外,视频游戏系统中的Web浏览器也开始日益普及。任天堂 Wii 和 Playstation 3 或者内置Web浏览器,或者提供了浏览器下载。Wii中的浏览器实际上是定制版的Opera,是专门为Wii Remote设计的。PlayStation的浏览器是自己开发的,没有基于前面提到的任何呈现引擎。这两个浏览器中的用户代理字符串如下:

    Opera/9.0 (Nintendo Wii;U; ; 1621; en) // 运行在Wii中的 Opera
    Mozilla/5.0 (PLAYSTATION 3; 2.00) // playstation 3
    

    为了检测这些设备,我们需要在client。system中添加适当的属性。

    var system = {
      // ...
      
      // 游戏系统
      wii: false,
      ps: false
    }
    

    检测代码

    system.wii = ua.indexOf('Wii') > -1
    system.ps = /playstation/i.test(ua)
    

    完整代码

    以下是完整的用户代理字符串检测脚本,包括检测呈现引擎,平台,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) { // OPera5+
        engine.ver = browser.ver = window.opera.version()
        engine.opera = browser.opera = parseFloat(engine.ver)
      } else if(/AppleWebKit\/(\S+)/.test(ua)) { // WebKit渲染引擎
        engine.ver = RegExp["$1"]
        engine.webkit = parseFloat(engine.ver)
    
        // 确定是 Chrome 还是 Safari
        if (/Chrome\/(\S+)/.test(ua)) { // Chrome 浏览器
          browser.ver = RegExp["$1"]
          browser.chrome = parseFloat(browser.ver)
        } else if (/Version\/(\S+)/.test(ua)) { // Safari
          browser.ver = RegExp["$1"]
          browser.safari = parseFloat(browser.ver)
        } else { // Safari 3以下的低版本,进行大概的版本估算
          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 (/KHMTL\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) { // KHTML 引擎,konqueror 浏览器
        engine.ver = browser.ver = RegExp['$1']
        engine.khtml = browser.konq = parseFloat(engine.ver)
      } else if(/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) { // Gecko引擎,Firefox
        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)) { // IE引擎
        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) { // windows平台
        if(/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) {
          if (RegExp["$1"] == "NT") { // 则有可能时 NT4.0 XP Vista 7
            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') { // ME
            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') {// 老版本 Windows Mobile
        system.winMobile = system.win
      } else if(system.win == 'Ph') { // Windwos Phone 7 或更新版本
        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
      }
    })();
    

    使用方法

    前面已经说过,用户代理检测是客户端检测的最后一个选择,优先选择能力测试和怪癖检测。

    • 不能直接准确地使用能力检测和怪癖检测。例如,浏览器实现了为将来功能预留的存根函数。在这种情况下,仅测试相应的函数是否存在还得不到足够的信息。
    • 同一款浏览器在不同平台下具备不同的能力。
    • 为了跟踪分析等目的需要知道确切的浏览器

    小结:

    • 能力测试:在编写代码之前先检测特定浏览器的能力,例如:在调用某个函数之前,先确定这个函数是否存在。
    • 怪癖测试:实际上是浏览器实现最后存在的bug,例如:早起的WebKit中会在for-in 循环中枚举被隐藏的属性。由于怪癖检测和能力检测相比效率更低,因此应该只在某个怪癖会干扰到脚本的情况下使用。怪癖检测无法精确地检测特定的浏览器和版本
    • 用户代理检测:通过检测用户代理字符串来识别浏览器。用户代理字符串中包含大量与浏览器相关的信息,包括平台、操作系统等。需要注意的是Opera浏览器,他会隐藏其用户代理字符串的情况。即便如此,通过用户代理字符串仍然能够检测出浏览器所用的呈现引擎以及所在平台,包括移动设备和游戏系统。

    相关文章

      网友评论

          本文标题:客户端检测

          本文链接:https://www.haomeiwen.com/subject/mwuklctx.html