这和什么比较像?
事实上,如果你了解JavaScript对象系统的机理,你就可以类比:这不就是原型嘛!原型系统就是利用了这种跌落——寻找某个成员,如果它在这个对象里定义了,就返回之;否则沿着原型链向上搜(没错,这次是向上的),如此重复,直到真的连原型链都到头的时候,返回个undefined。
说做就做!这里同样用addEvent为例。首先,我们定义一个空驱动,它里面什么都不包含:
var nullDriver = {}
然后,就是创建个对象,并且把原型链指向它。在ECMA V5时代,我们可以用Object.create,可惜,现在还有N多老客户端(否则做什么兼容啊),所以自己craft个函数:
var derive = Object.create ? Object.create: function() { var T = function() {}; return function(obj) { T.prototype = obj; return new T } }()
这个用法你可能会觉得很诡异,但它工作起来一点问题没有,速度也不慢——能达到Object.create的一半。我们就用这个derive开动:
var dhtmlDriver = derive(nullDriver); var dhtmlDriverBugfix = derive(dhtmlDriver);
这里的bugfix是针对一些“bug”和特殊情况定义的特别Driver。这里你可以忽略它。好了,DHTML里面addEvent是什么来着?
if (supportsAttachEvent) { dhtmlDriver.addEvent = function(e, what, how) { e.attachEvent('on' + what, how) } }
然后呢?位于原型链最前端的应该是W3C的标准驱动啊,写上!
var w3cDriver = derive(dhtmlDriverBugfix); var w3cDriverBugfix = derive(w3cDriver); if (supportsAddEventListener) { w3cDriver.addEvent = function(e, what, how) { e.addEventListener(what, how) } }
最后,我们就放个东西上去做最后调用的接口。(因为w3cDriverBugfix太难看……)
var driver = derive(w3cDriverBugfix);
然后就调用好了。看,这就让那些长得吓人的分支判断变得简单有效,但不失fallback本色:在支持addEventListener上调用addEvent等价于调用w3cDriver.addEvent,而在不支持addEventListener的客户端上就会跌落到底下,比如调用dhtmlDriver.addEvent。另外,进行bugfix也很容易——可以在专门的“bugfix”层进行hook,而原有层丝毫不受影响。
等等,继承这么多层
会很慢么?诚然,那么深的原型链肯定会慢,不过我有办法。还记得给对象的属性写入时会发生什么事情吗?
var ego = function(x) {return x} for (var each in driver) { if (! (each in nullDriver)) { driver[each] = ego(driver[each]) } }
没错,原来高企在原型链上面的方法会“哗”的一下掉到最下面!这回不用沿着原型链向上搜了,直接从最底端获取属性即可。这里用ego函数的原因是防止一些浏览器“优化掉”这里的代码。
总结
虽然这里谈兼容,可是,它的精华却在语言特性上——利用原型继承,我们可以很优雅地完成这个令人头疼的操作。是的,框架的美感不应该只在外表,其内部——即使是最最令人烦的内部——也同样要优雅。
这里的技术可以在dess中找到。
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 [...]