【JS红宝书学习】9客户端检测

因为浏览器间存在大大小小的差异以及怪癖(quirk),开发人员应当优先采取一种更通用的方法,然后在使用特定于浏览器的技术增强该方案。当然这是红宝书的观点,是一种渐进增强的观点,还有与之相对的优雅降级思想。
注意:检测Web客户端的方法和手段有很多,不到万不得已的时候就不要使用客户端检测,尽量去找通用的方法。

1、能力检测

最常用的客户端检测形式就是能力检测(也叫特性检测),能力检测的目的不是识别浏览器,而是识别浏览器的能力
举例,IE在早期版本没有document.getElementById()方法;

function getElement(id){
    if(document.getElementById){
        return document.getElementById(id);
        }
    else{
        return document.all[id];
        }
}

更可靠的能力检测:
按照上例,当给document对象定义了一个getElementById的属性的时候,那么即使没有getElementById()方法,也会执行if语句块内的代码,因此我们需要更可靠的能力检测,加入对这个成员的类型判断。

function getElement(id){
    if(typeof document.getElementById == "function" ){
        return document.getElementById(id);
        }
    else{
        return document.all[id];
        }
}

典型的错误能力检测:尝试检测浏览器
注意!!(双逻辑非操作符)可以自动将其他类型的值转换为布尔值。

// 不够具体
var isFirefox = !!(navigator.vendor && navigator.vendorSub);
// 假设过头
var isIE = !!(document.getElementById && document.createElement && document.getElementsByTagName);

第一个错误是因为不够具体,因为这两个属性也被Safari使用了,所以如果是Safari也被认为是Firefox。
第二个错误是因为假设过头了,当IE以后修改/删除这三个方法时,就会出现问题。
因此要检测能力,而非浏览器,或者想要检测浏览器时将能力检测组合使用。
正确的能力检测:

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

2、怪癖检测

怪癖检测(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;
}();

这段代码意味着,如果在o的属性中可以找到toString方法,就意味着没有这个bug。
可以把这段代码在开发者工具中运行一下,chrome返回的是false,意味着没有这个怪癖。
除此之外还有一个需要检测的怪癖是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,将会返回两个toString实例,count == 2,返回true。
放到chrome开发者工具中运行后,返回false

3、用户代理检测

这是争议最大的客户端检测技术。用户代理检测是通过用户代理字符串来确定实际使用的浏览器。在每次http请求过程中,用户代理字符串是作为相应首部发送的,而且该字符串可以通过js的navigator.userAgent属性访问。在服务端,通过检测用户代理字符串来确定用户使用的浏览器是一种常用并且广为接受的做法。而在客户端,用户代理检测的优先级在能力检测和怪癖检测之后。
用户代理检测的争议与电子欺骗(spoofing)有很大的关系。
所谓电子欺骗,就是指浏览器通过在自己的用户代理字符串加入一些错误或误导性的信息,来达到欺骗服务器的目的(尤其是爬虫可以通过这种方式绕开对爬虫的限制)。
1、用户代理字符串的历史
第一个用户代理字符串 Mosaic/0.9
Netscape navigator 2:
网景公司的格式 Mozilla/版本号 [语言] [平台;加密类型](mozilla是mosaic killer的缩写)
语言是指语言代码,表示应用程序针对哪一种语言设计的。
平台是指操作系统和平台,表示应用程序的运行环境。
加密类型:安全加密的类型。可以是U(128位加密)、I(40位加密)、N(未加密)。
示例:
Mozilla/2.02 [fr] (WinNT;I)

Netscape Navigator 3 和 Internet Explorer 3:
删去了语言标记,增加了操作系统或系统使用的CPU等可选信息。
Netscape Navigator3:
Mozilla/版本号 [平台;加密类型 [;操作系统或CPU说明]]
比如Mozilla/3.0 (Win95; U)
因为当时服务器都要检验用户代理字符串,而不支持IE自身的用户代理字符串,所以IE在最开始时做了对Mozilla的兼容:
Internet Explorer3:
Mozilla/2.0 (compatible; MSIE 版本号;操作系统)
比如IE3.02 Mozilla/2.0 (compatible; MSIE 3.02; Window95)

Netscape Communicator 4 和 IE4~8:
Netscape Communicator 4一直遵循着第三版的用户代理字符串格式。
Mozilla/版本号 [平台;加密类型 [;操作系统或CPU说明]]

IE7及以前和IE3没有区别,但前面Mozilla的版本号变成了4.0(这也是网景和微软版本号唯一一次重叠),IE8有增加了呈现引擎(Trident)的版本号。
Mozilla/4.0 (compatible; MSIE 版本号;操作系统;Trident/Trident版本号)
比如Mozilla/4.0 (compatible; MSIE 7.0;Windows NT 5.1;Trident/4.0)
这个新增的Trident记号是为了让开发人员知道IE8是不是在兼容模式下运行。如果是,则MSIE的版本号会变成7,但Trident及版本号还会留在用户代理字符串中。

IE9只是将Mozilla的版本号升为5.0,Trident版本号也升为5.0

Gecko:
Gecko是firefox的呈现引擎,格式相对复杂(加粗部分为必填选项):
Mozilla/Mozilla版本号 (平台加密类型操作系统或CPU语言;预先发行版本) Gecko版本号 应用程序或产品/应用程序或产品版本号
其中
预先发行版本是指最初用于表示Mozilla的预先发行版本,现在则用来表示Gecko呈现引擎的把版本号。
Gecko版本号是Gecko呈现引擎的版本号,但由yyyymmdd格式的日期表示。
应用程序或产品是指使用Gecko的产品名。可能是Netscape、Firefox。
应用程序或产品版本号是指应用程序或产品的版本号;用来区分Mozilla版本号和Gecko版本号。
比如Windows XP下的Netscape 6.21
Mozilla/5.0 (windows; U;Windows NT 5.1;en-US;rv:0.9.4) Gecko/20011128 Netscape6/6.2.1
到了后期Firefox4发布,又简化了用户代理字符串:
删除了语言标记。
在浏览器中使用强加密(默认设置)时,不显示加密类型。也就是说,不会出现U,只会 弧线I和N。
平台记号删除了,而在操作系统或CPU中始终包含Windows字符串。
Gecko版本号固定为20100101.
比如 Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox 4.0.1

WebKit:
WebKit是Apple旗下Safari浏览器的呈现引擎,原本是Konqueror的一个分支,后来独立了出来。
和IE3一样,也是对Mozilla做了兼容。格式如下:
Mozilla/5.0 (平台;加密类型;操作系统或CPU;语言) AppleWebKit/AppleWebKit版本号 (KHTML,like Gecko) Safari/Safari版本号
比如 Mozilla/5.0 (Macintosh;U;PPC Mac OS X;en) AppleWebKit/124 (KHTML,like Gecko) Safari/125.1
Safari1最受诟病的就是(KHTML,like Gecko),明显就是欺骗客户端和服务器要将Safari当作Gecko来处理,最后好像也没有改。
Safari3新增了Version记号,用来标识Safari实际的版本号。
比如Mozilla/5.0(Macintosh;U;PPC Mac OS X;en) AppleWebKit/522.15.5 (KHTML,like Gecko) Version/3.0.3 Safari/522.15.5

Konqueror:
与KDE Linux集成的Konqueror,是一款居于KHTML开源呈现引擎的浏览器。尽管Konqueror只能在Linux中使用,但也有较多的用户。Konqueror也效仿IE进行了兼容。格式如下:
Mozilla/5.0 (compatible; Konqueror/版本号; 操作系统或CPU)
为了与WEbKit用户代理字符串的变化保持一致,又增加了部分标记:
Mozilla/5.0 (compatible;Konqueror/3.5;操作系统或CPU) KHTML/KHTML版本号 (like Gecko)
比如
Mozilla/5.0 (compatible;Konqueror/3.5;Sun OS;) KHTML/3.5.0 (like Gecko)

Chrome:
Chrome使用的也是Webkit作为呈现引擎,但是使用了不同的JavaScript引擎。在Chrome0.2这个最初的beta版时,用户代理字符串完全取自WebKit,只添加了一段表示Chrome的版本号。
Mozilla/5.0 (平台;加密类型;操作系统或CPU;语言) AppleWebKit/AppleWebKit版本号 (KHTML,like Gecko) Chrome/Chrome版本号 Safari/Safari版本号
比如Chrome7:
Mozilla/5.0 (Window;U;Windows NT 5.1; en-US) AppleWebKit/AppleWebKit534.7 (KHTML,like Gecko) Chrome/Chrome7.0.517.44 Safari/Safari534.7

Opera:
Opera默认的用户代理字符串是所有现代浏览器中最合理的,因为它正确地标识了自身及其版本号。
在Opera8.0之前,其用户代理字符串采用如下格式:
Opera/版本号 (操作系统或CPU;加密类型)[语言]
比如Window XP中的Opera 7.54:
Opera/5.0 (Windows NT 5.1;U;) [en]
Opera8后将语言部分移到了小括号内。
Opera/7.54 (Windows NT 5.1;U;en)
比如Window XP中的Opera 8.0:
Opera/8.0 (Windows NT 5.1;U;en)
在Opera9之后,Opera也是遇到了兼容性的问题。只好将自己标识成Firefox或者IE(这也是为什么这段时间Opera又备受争议)。
Mozilla/5.0 (Windows NT 5.1;U;en;rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50
Mozilla/4.0 (compatible;MSIE 6.0;Windows NT 5.1;en) Opera 9.50
Opera 10:
Opera/9.80 (操作系统或CPU;加密类型;语言) Presto/Presto版本号 Version/版本号
比如运行在Windows7中的Opera10.63
Opera/9.80 (Windows NT 6.1; U ;en)Presto/2.6.30 Version/10.63

iOS/Android:
移动操作系统iOS和Android的浏览器都是基于WebKit,并且共享相同的基本用户代理字符串格式。
iOS格式如下:
Mozilla/5.0 (平台;加密类型;操作系统或CPU ;like Mac OS X;语言) AppleWebKit/AppleWebKit版本号 (KHTML,like Gecko) Version/浏览器版本 Mobile/移动版本号 Safari/Safari版本号。
比如iphone
Mozilla/5.0 (iPhone;U;CPU iPhone OS 3_0 like Mac OS X;en-us) AppleWebKit/528.18 (KHTML,like Gecko) Version/4.0 Mobile/7A341 Safari/528.16
注意在iOS3前,用户代理字符串中不会出现操作系统版本号。
Android的格式与iOS相似,只是没有移动版本号(但是有个Mobile的记号)
比如Google的Nexus One手机
Mozilla/5.0 (Linux;U;Android 2.2;en-us;Nexus One Build/FRF91)AppleWebKit/533.1 (KHTML,like Gecko) Version Mobile Safari/533.1

总结一下呈现引擎对应的浏览器:
Gecko引擎:Firefox、Camino、Netscape浏览器;
IE引擎:IE浏览器;
Webkit引擎:Safari、Chrome浏览器、移动浏览器(IOS、Android);
Opera引擎:Opera浏览器;
KHTML引擎:Konqueror浏览器。

附检测脚本

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,
  }
})()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值