代码改变世界

[Javascript]客户端检测

2013-04-26 17:19  Hejin.Wong  阅读(2183)  评论(1编辑  收藏  举报

本文地址:http://www.cnblogs.com/egger/archive/2013/04/26/3045285.html

客户端检测是一种行之有效的开发策略。但不到万不得已,就不要使用客户端检测。先设计通用的方案,然后根据浏览器之间的差异和各自的怪癖quirky,再使用特定于浏览器的技术增强该方案。

能力检测 Feature Detection

能力检测(又称特性检测)目标是识别浏览器的能力而不是识别特定的浏览器。只需根据浏览器支持的功能给出解决方案。

能力检测作为确定下一步解决方案的依据,而不是用它来判断用户使用的是什么浏览器。

基本模式:

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

两个重要的概念:
第一个概念是先检测达成目的的最常用的特性。先检测最常用的特性,可以保证代码最优化,因为在多数情况下都可以避免测试多个条件。
第二个概念是必须测试实际要用到的特性。一个特性存在,不一定意味着另一个特性也存在。

 

更可靠的能力检测

确定一个对象是否支持排序,检测对象是否支持sort()方法。

//只检测了是否存在相应的方法
function isSortable(object){
       return !!object.sort;  
}

var result= isSortable({sort:true});

检测某个属性是否存在并不能确定对象是否支持排序。,检测sort是不是一个函数。

function isSortable(object){
       return  typeof object.sort == "function ";  
}

尽量使用typeof进行能力检测

function hasCreateElement(){
       return  typeof document.createElement =="function ";  
}

IE8之前的版本返回false.因为DOM对象是宿主对象,是通过COM而不是JScript实现的,document.createElement()函数是一个COM对象。IE9已更正。

 var xhr=new ActiveXObject("Microsoft.XMLHttp");
 if(xhr.open){ //error
     //TO-DO
 }

直接把函数属性访问会导致JS错误。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

检测某个或某几个特性并不能够确定浏览器。实际上,根据浏览器不同将能力组合起来是更可取的方式。如果知道自己的应用程序需要使用某些特定的浏览器特性,那么最好是一次性检测所有相关特性,而不要分别检测。

//确定浏览器是否支持 Netscape 风格的插件
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length );

//确定浏览器是否具有 DOM1 级规定的能力
var hasDOM1 = !!(document.getElementById && document.createElement 
                && document.getElementByTagName);

 

延伸:

http://peter.michaux.ca/

http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting

怪癖检测  Quirkys Detection

目标识别浏览器特殊的行为,与能力检测不同,是想知道浏览器存在的什么缺陷.运行一小段代码,以确定某一特性不能正常工作.

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

例如,IE中存在的一个 bug ,即如果某个实例属性与标记为 [[DontEnum]] 的某个原型属性同名,那么该实例属性将不会出现在 fon-in 循环当中。可以使用如下代码来检测这种“怪癖”:

var hasDontEnumQuirk = function(){
    var o = { toString : function(){}};

    for( var prop in o){
        if( prop == "toString"){
            return false;
        }
    }
    return true;
}();

 

用户代理检测

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

  电子欺骗(spoofing)是指 浏览器通过在自己的用户带来字符串加入一些错误或误导性信息,达到欺骗服务器的目的。

  阅读下面内容最好事先了解下用户代理字符串的知识,请阅读博文《用户代理字符串简史》。 

用户代理字符串检测技术

  当你了解用户代理字符串发展和使用方式后,使用客户端检测技术检测出特定的浏览器不是一个轻松的事情。一般知道呈现引擎和最低限度的版本就足以决定正确的操作方法。

1.识别呈现引擎

  确切的知道浏览器的名字和版本号不如确定他们使用的是什么呈现引擎。如果Firefox、Camino 和 Netsacpe 都使用相同版本的 Gecko ,那么他们一定支持相同的特性。类似的,不管是什么浏览器,只要它跟 Safari 3 使用的是同一个版本的 WebKit,那么该浏览器也就跟 Safari 3 具备同样的功能。因此,我们需要编写的脚本将主要检测五大呈现引擎: IE、Gecko、WebKit、KHTML 和 Opera1

  注1:现在Opera 和 Chrome将采用基于WebKit开发自主渲染引擎Blink。

  模块增强模式封装检测脚本。

var client = function(){
//呈现引擎 var engine = { ie : 0, gecko : 0, webkit : 0, khtml : 0, opera : 0, //具体的版本号 ver : null }; //再次检测呈现引擎、平台和设备 return { engine : engine }; }();

  如果检测到那个呈现引擎,就以浮点数值形式将该引擎的版本号写入相应的属性。而呈现引擎的完整版本是一个字符串,则被写入ver属性。

if(client.engine.ie){   //如果是IE ,engine.ie的值应大于0
    //针对IE
} else if (client.engine.gecko > 1.5) {
    if(client.engine.ver == "1.8.1"){
        //to-do
    }
    
}

  检测到一个呈现引擎后,其 client.engine 中对应的属性将被设置成一个大于 0 的值,该值可以转换成布尔值的 true。这样就可以在 if 语句中检测相应的属性,以确定当前使用的呈现引擎,连具体的版本号都不需要考虑。鉴于每个属性都包含一个浮点数值,因此有可能丢失某些版本信息。例如,将字符串"1.8.1"传入 parseFloat() 后悔得到数值 1.8。不过,在必要的时候,可以检测 ver 属性,该属性中保存着完整的版本信息。

  正确的识别呈现引擎关键是检测顺序要正确。

  首先应该检测的是 Opera,因为它的用户代理字符串可能完全模仿其他浏览器。任何情况下其用户代理字符串不会将其标识为(详情请阅读博文《用户代理字符串简史》),就是所谓信不信由你,反正我是不信。

//Opera 5起支持window.opera对象
if ( window.opera ){
    engine.ver = window.opera.version();  //Opera 7.6起返回浏览器版本字符串
    engine.opera = parseFloat( engine.ver );
}

  其次检测 WebKit.因为WebKit的用户代理字符串包含“Gecko”和“KHTML”,若先检测他们,可能会出现错误的结论。

//AppleWebKit独一无二
var ua = navigator.userAgent;

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

  仅供参考,不保证精确。

Safari   最低限度的WebKit版本号 Safari   最低限度的WebKit版本号
 1.0-1.0.2  85.7 1.3.2   312.8
 1.03 85.8.2  2.0   412
1.1-1.1.1  100  2.0.1  412.7 
1.2.2  125.2  2.0.2  416.11 
1.2.3  125.4  2.0.3 417.9 
1.2.4  125.5.5  2.0.4 418.8 
1.3  312.1  3.0.4  523.10 
1.3.1 321.5  3.1  525 

 

  接下来测试 KHTML。KHTML的用户代理字符串包含“Gecko”,所以在排除KHTML之前是无法正确检测基于Gecko的浏览器。Konqueror 3.1及更早的版本不包含KHTML的版本,要使用Konqueror的版本来替代。

var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
}

   下面我们就可以准确的检测Gecko。Gecko的版本号位于字符串“rv:”与一个闭括号之间,还要查找“Gecko/”后的8个数字。

//Windows XP 下的 Firefox 2.0.0.11 :
Mbzilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.
var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
} else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);
} 
Firefox版本号  最低限度的Gecko版本号 Firefox版本号  最低限度的Gecko版本号
1.0 1.7.5 3.5 1.9.1
1.5 1.8.0 3.6 1.9.2
2.0 1.8.1 4.0 2.0.0
3.0 1.9.0    

最后是IE.IE版本号位于MSIE的后面。

var ua = navigator.userAgent;

if ( window.opera ){
    engine.ver = window.opera.version();
    engine.opera = parseFloat( engine.ver );
} else if ( /AppleWebKit\/(\S+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.webkit = parseFloat(engine.ver);
} else if ( /KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.khtml = parseFloat(engine.ver);
} else if ( /rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
    engine.ver = RegExp["$1"];
    engine.gecko = parseFloat(engine.ver);
} else if (/MSIE ([^;]+)/.test(ua)){
    engine.ver = browser.ver = RegExp["$1"];
    engine.ie = browser.ie = parseFloat(engine.ver);
}

 2.识别浏览器

  识别了浏览器的呈现引擎就足以为我们采取正确的操作提供依据了。可是,只有呈现引擎还不能够说明存在所需的JavaScript功能。相同内核的浏览器的JavaScript有可能引擎不一样:safari 和chrome。

var client = function(){
    //呈现引擎

    var engine = {
        ie : 0,
        gecko : 0,
        webkit : 0,
        khtml : 0,
        opera : 0,

        //具体的版本号
        ver : null
    };

    var browser = {
        //浏览器
        ie : 0,
        firefox : 0,
        konq : 0,
        opera : 0,
        chrome : 0,
        safari : 0,

        //具体的版本
        ver : null
    };

    //再次检测呈现引擎、平台和设备

    return {
        engine : engine,
        browser : browser
    };
}();

  由于大多数浏览器与其呈现引擎密切相关,所以下面示例中检测浏览器的代码与呈现引擎的代码是混合在一起的

//检测呈现引擎及浏览器
var ua = navigator.userAgent;

if ( window.opera ){
    
    engine.ver = window.opera.version();
    engine.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"];
        borwser.safari = parseFloat(browser.ver);
    } else {
        //近似的确定版本号
        var safariVersion = 1;

        if (engine.webkit < 100 ){
            safariVersion = 1;
        } else if (engine.webkit < 312){
            safariVersoin = 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 = RegExp["$1"];
    engine.khtml = 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);
}

 我们就可以这样判断了:

if(client.engine.webkit){   
    if (client.browser.chrome) {

    }else if (client.browser.safari){

    }
} else if (client.engine.gecko) {
    if (client.browser.firefox) {

    }else{
    
    }
}

3.识别平台

  很多时候,只要知道呈现引擎就可以编写出适合的代码了,但在有些情况下,平台可能是必须关注的问题。三大主流平台:Windows、Mac、Unix(Linux)为了检测这些平台,需要再添加一个对象:

var client = function(){
     //呈现引擎
 
     var engine = {
         ie : 0,
         gecko : 0,
         webkit : 0,
         khtml : 0,
         opera : 0,
 
         //具体的版本号
         ver : null
     };
 
     var browser = {
         //浏览器
         ie : 0,
         firefox : 0,
         konq : 0,
         opera : 0,
         chrome : 0,
         safari : 0,
 
         //具体的版本
         ver : null
     };
 
     var system = {
         win : false,
         mac : false,
         xll : false
     };
 
     //再次检测呈现引擎、平台和设备
 
     return {
         engine : engine,
         browser : browser,
         system : system
     };
 }();
 
 //navigator.platform值Win32 、Win64、MacPPC、MacIntel、X11、Linux i686
 var p = navigator.platform;
 system.win = p.indexOf("Win") == 0;
 systemp.mac = p.indexOf("Mac") == 0;
 system.xll = (p.indexOf("Xll")) == 1 || (p.indexOf("Linux") == 0);