【转】用探测技术编写跨浏览器的Javascript代码
作者:Tony Ross(Program Manager)
原文:Same Markup: Writing Cross-Browser Code
翻译:一回(csser.com)
基本原则
- 推荐
- 功能探测:使用某个功能前先检测浏览器是否支持它
- 行为探测:在应用之前对已知问题进行测试
- 不推荐
- 探测指定浏览器:根据浏览器的唯一性来改变页面行为
- 臆断不相关的功能:根据一个功能的探测去应用另一个不同的功能
上面所列的原则很重要,原因是当今的大部分页面都会混合适合不同浏览器的代码,这些混合代码用于判断什么情况下运行哪些程序,以适合不同的浏览器,最基本的就是脚本判断,通常看起来像下面的样子
if ( condition ) {
// 主要代码 csser.com
} else {
// 备用代码
}
上面代码用到的判断条件(condition),很多情况下不是基于一个给定的功能是否可用,而是基于用户使用的哪个浏览器,这样会存在一个问题: 备用代码是基于指定的浏览器而执行,从而限制了网页的适应性。最终结果将是一旦新的浏览器发布,页面功能将会受影响。同时另外的情况是,即使新版本浏览器 增加了新的功能,代码仍将沿用旧的方法,我们通过下面的例子来说明这个问题:
事件注册示例(基于浏览器探测的实现:不推荐)
你可以很容易的测试下面代码的效果,示例代码通过探测浏览器(差的实践)切换事件模型。
// 事件处理示例函数
function f1() { document.write("addEventListener was used by csser.com"); }
function f2() { document.write("attachEvent was used by csser.com"); }
// 不被推荐:探测指定浏览器
if(navigator.userAgent.indexOf("MSIE") == -1) {
window.addEventListener("load", f1, false);
} else {
window.attachEvent("onload", f2);
}
结果,IE9下输出:attachEvent was used by csser.com。很明显,即使IE9已经支持addEventListener,但仍然不会被使用
事件注册示例(基于功能探测的实现:推荐)
下面的代码通过功能探测切换事件模型,与检测IE浏览器不同的是,它检测addEventListener功能是否可用,该代码不仅在不支持 addEventListener的古代浏览器下可用,更重要的是,只要浏览器支持addEventListener功能就可以被使用。
// 事件处理示例函数
function f1() { document.write("addEventListener was used by csser.com"); }
function f2() { document.write("attachEvent was used by csser.com"); }
// 推荐:功能探测
if(window.addEventListener) {
window.addEventListener("load", f1, false);
} else if ( window.attachEvent ) {
window.attachEvent("onload", f2);
}
要做到让适合的代码在适合的浏览器下运行,这就是为什么功能探测在网页和框架中越来越多被采用的原因。功能探测允许跨浏览器的代码顺利的执行,而不 需要你完全弄明白不同浏览器的不同版本的具体能力。jQuery就是一个几乎完全依赖功能探测的Javascript框架,事实上,jQuery.support文档详细的向我们介绍了如何在你的网站使用功能探测。
行为探测(jQuery的getElementById示例:推荐)
除了直接的功能探测,jQuery也利用了行为探测,它通过运行一段存在已知问题的代码来确定是否需要变通的解决方法。下面的是从jQuery源代 码取出的经过修改的片段,其用途是测试getElementById是否返回包含相同name特性的元素,这是古老IE浏览器的Bug,IE8已经修复。
// 动态创建一个包含name特性的超链接元素,并放置在动态创建的div元素内
// 文档中该a元素的name值是唯一的
var form = document.createElement("div"),
id = "script" + (new Date).getTime();
form.innerHTML = "<a name='" + id + "'/>";
// 将创建的div元素插入页面文档的最前端
var root = document.documentElement;
root.insertBefore( form, root.firstChild );
// 通过getElementById传入唯一的id值,如果存在匹配的元素,说明返回了name值的元素
if ( document.getElementById( id ) ) {
// ... 代码...
// 用于测试的代码
document.write("getElementById workaround was used by csser.com");
}
// 用于测试的代码
else document.write("No workaround was used by csser.com");
root.removeChild( form );
臆断不相关的功能(可实践性:差)
最后一点我需要讲到的是,臆断不相关的功能,先上代码,然后看看在不同IE版本浏览器下的结果:
// try-catch语句块用于捕捉IE8下的执行错误
try {
function fn() {alert("www.csser.com");}
if(window.postMessage) {
window.addEventListener("message", fn, false);
// 示例代码
document.write("Message事件注册成功");
} else {
// 当postMessage不可用时的代码
// 示例代码
document.write("不支持postMessage功能");
}
} catch(e) {
document.write("Message事件注册失败");
}
上例的代码在IE7-9下的执行结果分别为:
IE7 :不支持postMessage功能
IE8 : Message事件注册失败
IE9 : Message事件注册成功
本示例中的错误在于,编写者认为浏览器支持postMessage就会支持addEventListener,但事实上postMessage功能在IE8就被支持,但addEventListener是从IE9才被加入的。经过正确修复的代码如下:
function fn() {alert("www.csser.com");}
if(window.postMessage) {
if(window.addEventListener) {
window.addEventListener("message", fn, false);
} else if(window.attachEvent) {
window.attachEvent("onmessage", fn);
}
// 示例代码
document.write("Message事件注册成功");
} else {
// 当postMessage不可用时的代码
// 示例代码
document.write("不支持postMessage功能");
}