更优雅的兼容

对于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!')
}

换言之,过程是:

  • 如果客户端支持新接口,就将兼容层绑定到新接口上
  • 否则,如果客户端支持老接口/不一致接口,就将兼容层绑定到老接口上
  • 否则,如果可以的话,给出错误回馈

亦即,兼容层程序是从高空“掉”下来,如果客户端支持“高级”特性(新接口、标准接口)就将它“接住”——兼容层就有了归宿;否则继续向下掉——哦,老接口接住了,就用老接口;如果一直没人接住,于是——啪——摔倒了地上,并且用最后一口气喊一声:“你用的客户端太小众,我拿你没办法了!”

This entry was posted in Browser, 中文 and tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

15 Comments

  1. hehe123
    Posted August 10, 2010 at 3:33 pm | Permalink

    infinte 精彩的思路,和意想不到的解决方式,让我眼前一亮。 可惜没有抓住灵感。

  2. Posted August 11, 2010 at 2:02 am | Permalink

    很好的文章,我也在网上见过介绍类似技术的BLOG,可惜忘了出处了,这博文我收藏了

  3. Posted August 11, 2010 at 2:19 am | Permalink

    回了一篇博客:http://lifesinger.org/blog/2010/08/graceful-is-hard/
    感谢 infinte 的尝试

  4. tcdona
    Posted August 11, 2010 at 3:30 am | Permalink

    漂亮,真是佩服,对js的奇妙运用

  5. Posted August 11, 2010 at 5:00 am | Permalink

    灰常好的一篇文章…

  6. Posted August 11, 2010 at 6:22 am | Permalink

    好文,收藏到 XXX…

    -,-… 绕了好大的圈。

  7. Posted August 12, 2010 at 3:52 am | Permalink

    http://www.slideshare.net/jaffathecake/optimising-where-it-hurts-jake-archibald

    这里47页开始有个关于深层次作用域链对性能影响的测试.有详细的结论和测试方法.
    在作用域链层次大于5的时候,其在IE8对速度的影响突然变高.

    深层次的原型链可能不一样,但可以应用类似的方法拿准确的数据说话
    如果性能没问题,支持这种设计.

    • Posted August 12, 2010 at 4:11 am | Permalink

      最后有个下压就是干这个的,优化性能。

      • Posted August 12, 2010 at 6:43 am | Permalink

        是逐层复制引用下来?
        在访问属性的时候很好.
        在创建实例时候的性能消耗怎样?

      • Belleve Invis
        Posted August 12, 2010 at 10:21 am | Permalink

        我的天啊,driver是单件哎……不用创建实例。

      • Posted August 13, 2010 at 3:04 am | Permalink

        呵呵 好的 明白了.我以为会按类似的思路开发组件..

  8. akira
    Posted August 12, 2010 at 6:36 am | Permalink

    最后那个下压
    var ego = function(x) {return x}
    for (var each in driver) {
    if (! (driver.hasOwnProperty(each))) {
    driver[each] = ego(driver[each])
    }
    }

  9. Posted September 1, 2010 at 1:11 am | Permalink

    利用JS的原型继承实现更优雅的兼容

  10. Posted October 4, 2010 at 7:25 pm | Permalink

    这个应该就是不唐突和渐进增强,检查能力而不是检查浏览器。

  11. fd
    Posted September 23, 2011 at 2:06 am | Permalink

    晕 换汤不换药……

2 Trackbacks

  1. By 更优雅的兼容 « Coro/infinte on August 10, 2010 at 2:56 pm

    [...] 对于JS框架开发中的客户端(浏览器)兼容难题,各位想必都不陌生。平常,我们都用if去面对接口不一致以及成堆的bug。然而,这里介绍的方法却可以让兼容更加优雅。 原文地址 [...]

  2. By javascript事件机制 « Plane Art on April 3, 2011 at 6:47 pm

    [...] javascript 跨浏览器的事件系统(司徒正美。他博客有一系列的讲解) 更优雅的兼容(BELLEVE [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>