编写跨浏览器代码的核心原则

以下原则来自 IEBlog
  • DO
    • 特性检测(Feature Detection):使用之前检测浏览器是否支持这个特性。
    • 行为检测(Behavior Detection):应用解决方法时先测试已知问题。
  • DON'T
    • 检测具体浏览器(Detect Specific Browsers):即浏览器检测。不要根据浏览器标识(如 navigator.userAgent)应用页面行为。
    • 假定无关特性(Assume Unrelated Features):检测到一个特性存在就假定其他特性也存在。

基本应用

能力检测

能力检测Feature Detection)就是识别浏览器的能力,根据不同的能力支持给出不同的解决方案。例如 IE 5.0 之前的版本不支持 document.getElementById() 这个 DOM 方法,可以编写一个能力检测函数:
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!")
   }
}
能力检测需要注意两点:
  • 检测达成目标的最常用特性,避免测试多个条件。比如上面先检测标准的 DOM 方法。
  • 检测实际用到的特性。不能检测到一个特性存在,就假定另外一个特性也存在(即假定无关特性原则)。

更可靠能力检测

在可能的情况下,应该尽量使用 typeof 进行能力检测。因为对象在具有某个属性而不是方法时也会返回 true 。

需要注意的是,由于浏览器 bug 或者特性实现方式,typeof 也会返回不合理的值。

typeof document.createElement

IE8 及以前版本中上面的代码返回 object 而不是 function,因为这些版本的 DOM 功能通过 COM 对象实现。 IE9+ 修正了这个问题,所有 DOM 方法都返回 function。

IE 的 ActiveX 对象还有一个问题:

var xhr = new ActiveXObject("Microsoft.XMLHttp");
alert(typeof xhr.open); // 返回 "unknown",IE10 也是

因此,在浏览器环境下测试对象的某个特性可以使用 Peter Michaux 的方法:

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

// 使用示例
result = isHostMethond(xhr,"open") // true

还需要注意的是,能力检测不是浏览器检测,检测某个或某几个特性并不能够确定浏览器。如果需要使用某些特定特性,最好是一次性检测相关特性,而不要分别检测。

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

// 检查浏览器是否具备 DOM1 规定能力
var hasDOM1 = !!(document.getElementById && document.createElement &&
               document.getElementsByTagName);

怪癖检测

怪癖检测(quirks detection),又称行为检测,即检测浏览器存在什么缺陷(怪癖也就是bug)。通常是运行一段代码,以确定某个特性不能正常工作。怪癖一般是个别浏览器独有的,对有程序有直接影响的怪癖,最好在脚本一开始就执行检测。

如 IE8 及更早版本的一个 bug,即如果某个实例属性与标记为 [[DontEnum]] 的某个原型属性同名,那么该属性不会出现在 for-in 循环中。

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

正确的 ES 实现中,toString 应该在 for-in 循环中作为属性返回。

另外一个经常需要检测的“怪癖”是 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,那么 for-in 循环枚举带有自定义的 toString() 方法的对象就会返回两个 toString 实例。

更多参考