对于JS框架开发中的客户端(浏览器)兼容难题,各位想必都不陌生。平常,我们都用if去面对接口不一致以及成堆的bug。然而,这里介绍的方法却可以让兼容更加优雅。
问题种种
做底层接口兼容,无非就是利用if,判断客户端支持哪个接口的问题。最著名的例子就是事件:
var addEvent = function(e, what, how) { if (e.addEventListener) e.addEventListener(what, how, false) else if (e.attachEvent) e.attachEvent('on' + what, how) }
这里考虑了给元素绑定事件时可能遇到的两种状况——标准的W3C DOM接口以及DHTML提供的接口。当然这个例子还很粗糙,但足够说明问题了。
原先的方法是在兼容层调用有现场判断并进入相应的if分支。很显然,这种“现场判断”的方法效率并不高。后来,人们采用这样的办法:
if (MSIE) { addEvent = function(e, what, how) { e.attachEvent('on' + what, how); } } else { addEvent = function(e, what, how) { e.addEventListener(what, how); } }
在一次判断后给addEvent绑定不同的代码,从而免去了运行时的分支判断。
很可惜,这个问题也不小。首先把“采用attachEvent”和“客户端是MSIE”绑定在一起是个很过时的想法。假如微软哪天良心发现了怎么办?这事情现在就发生了——IE9明确支持了DOM接口,甚至DOM3都支持。结果,就这个“良心发现”的举动会毁掉许多前端库,他们必须被迫修改代码(如同IE8来时那样)。况且这种做法没有考虑“未知的客户端”——据我所知,Google发布Chrome后也导致不少类库重写代码。
特性检测
那究竟该怎么做?特性检测就可以最大限度地避免“新客户端”带来的麻烦——通过一组在类库初始化时定义的代码来检测客户端拥有的特性,并利用这一组检测值绑定类库代码:
var supportsAddEventListener = !!(checkerElement.addEventListener); if (supportsAddEventListener) { addEvent = function(e, what, how) { e.addEventListener(what, how); } } else if (supportsAttachEvent) { addEvent = function(e, what, how) { e.attachEvent('on' + what, how); } }
特性检测实际上是将“使用某个客户端”和“支持某个特性”进行解耦——让if分支直接针对“特性有无”(接口是否一致)判断,从而消除客户端制造商“良心发现”造成的“好心办坏事”。事实上这么做也是符合历史潮流之选——当标准接口逐渐普及,客户端之间渐渐“表征一致”时,为什么不做个一致的兼容层接口呢?
跌落
让我们重新看看这些代码。通常,一条利用特性检测进行兼容的代码往往是这样:
if (new_interface_detected) { comp = function() {uses_new_interface}; } else if (old_interface_detected) { comp = function() {uses_old_interface}; } else { throw new Error('Unadaptable!') }
换言之,过程是:
- 如果客户端支持新接口,就将兼容层绑定到新接口上
- 否则,如果客户端支持老接口/不一致接口,就将兼容层绑定到老接口上
- 否则,如果可以的话,给出错误回馈
亦即,兼容层程序是从高空“掉”下来,如果客户端支持“高级”特性(新接口、标准接口)就将它“接住”——兼容层就有了归宿;否则继续向下掉——哦,老接口接住了,就用老接口;如果一直没人接住,于是——啪——摔倒了地上,并且用最后一口气喊一声:“你用的客户端太小众,我拿你没办法了!”
15 Comments
infinte 精彩的思路,和意想不到的解决方式,让我眼前一亮。 可惜没有抓住灵感。
很好的文章,我也在网上见过介绍类似技术的BLOG,可惜忘了出处了,这博文我收藏了
回了一篇博客:http://lifesinger.org/blog/2010/08/graceful-is-hard/
感谢 infinte 的尝试
漂亮,真是佩服,对js的奇妙运用
灰常好的一篇文章…
好文,收藏到 XXX…
-,-… 绕了好大的圈。
http://www.slideshare.net/jaffathecake/optimising-where-it-hurts-jake-archibald
这里47页开始有个关于深层次作用域链对性能影响的测试.有详细的结论和测试方法.
在作用域链层次大于5的时候,其在IE8对速度的影响突然变高.
深层次的原型链可能不一样,但可以应用类似的方法拿准确的数据说话
如果性能没问题,支持这种设计.
最后有个下压就是干这个的,优化性能。
是逐层复制引用下来?
在访问属性的时候很好.
在创建实例时候的性能消耗怎样?
我的天啊,driver是单件哎……不用创建实例。
呵呵 好的 明白了.我以为会按类似的思路开发组件..
最后那个下压
var ego = function(x) {return x}
for (var each in driver) {
if (! (driver.hasOwnProperty(each))) {
driver[each] = ego(driver[each])
}
}
利用JS的原型继承实现更优雅的兼容
这个应该就是不唐突和渐进增强,检查能力而不是检查浏览器。
晕 换汤不换药……
2 Trackbacks
[...] 对于JS框架开发中的客户端(浏览器)兼容难题,各位想必都不陌生。平常,我们都用if去面对接口不一致以及成堆的bug。然而,这里介绍的方法却可以让兼容更加优雅。 原文地址 [...]
[...] javascript 跨浏览器的事件系统(司徒正美。他博客有一系列的讲解) 更优雅的兼容(BELLEVE [...]