Qomolangma实现篇(三):兼容层设计

一、Qomolangma的兼容层设计

自Qomo Field Test3开始,系统从内核一层开始提供跨(浏览器)平台兼容的能力。Qomo试图在不同的浏览器上提供相同(或类同)的框架层特性。

Qomo 的兼容层设计跟常见的其它系统并不一致。基于“Qomo是可拆卸可组装的”这样一个基本思想,Qomo实现的兼容层完全可以“象插件一样增强,或者卸载”。

与一般的系统不同,Qomo基本上是基于IE 6的JScript语言特性来提供的兼容层支撑。也就是说,如果你向Qomo的兼容层添加一个插件,那么你应该致力于“使第三方浏览器表现得跟IE6一致”。而通常的框架可能会反过来,要求通过兼容层来使得框架表现得跟ECMA-JavaScript一致。Qomo没有选择这样做,只是因为Qomo原本就更接近于IE,而非其它。

事实上,我要说明的是,让框架兼容IE,比让IE上的JScript兼容ECMA更难。因为JScript提供了很多ECMA不具备的特性。也就是说,Qomo选择了这种兼容设计,意味着实现一个更强的Java-Script环境。而不是“让IE的JScript表现起来更象JavaScript”。

Qomo上的兼容层设计,已经使Qomo可以正常地在IE5.0中运行了。此外,你会发现,Qomo已经在以下系统上被测试,并得到良好的支持:

  • 以Mozilla(Geckco engine)为内核的浏览器环境,例如Firefox
  • Mac上的safari
  • Windows平台上的IE5.0, IE5.5, IE6.0, IE7.0(beta1&2)

(注: 目前Qomo仅是在JavaScript层面上解决了兼容层问题。对于DOM,Qomo还没有展开设计。)

二、Qomolangma的兼容层框架

在Qomo的上一个Field Test中,兼容代码和unicode相关的解码是放在system.js中的。但后来Qomo团队发现这种设计导致system.js结构非常不清晰,移植性也极差。因此,在Field Test3之后的版本中,兼容层被抽取出来,用独立的“兼容层框架”来导入兼容代码。此外,unicode相关的解码被放置到与具体版本相关的“兼容代码”中。

作为一项参考,Qomo的标准设计环境中是这样:IE 5.5+浏览器。在这种环境下,透过兼容层支持:

Geckco engine 1.8.0.1
(Mozilla firefox 1.5.0.1以后)
  - 没有unicode解码特性,因此要求HTML&JS使用UTF-8编码
  - 使用相对路径系统(因为浏览器对(本地)绝对路径做出了严格的安全限制)

Internet Explorer
(IE5, 5.5, 6.0及以后)
  - 自动、智能地支持unicode解码特性。但对于IE5.0使用特殊的解码方案
  - 使用绝对路径系统,对路径/命名空间的识别和支持更准确和完善

Qomolangma通过在system.js中导入Compat/CompatLayer.js来装载兼容性框架。CompatLayer.js用于实现:

  • 识别浏览器及版本
  • 载入指定版本浏览器的兼容代码

三、Qomolangma如何识别浏览器及版本

当然,大多数浏览器相关的书籍都要求分析navigator.userAgent字符串来识别浏览器及版本。

但是Qomo没有(完全)采用这种设计。Qomo使用了一种被称为“Object Sniffing”的、准确而高效的技术来实现浏览器的识别。在CompatLayer.js头部的注释中,你可以看到与之相关的信息。

但是Object Sniffing还是有力所不逮之处。例如识别Firefox v1.5.0与v1.5.0.1。这两个版本只修正了一些极细微的bug。因此Object Sniffing是不能识别的。这种情况下,Qomo仍然使用分析userAgent的技术。为此,Qomo提供了一个内置于CompatLayer.js中的checkit()函数。你可以看看这个有趣的设计:checkit(v1, v2)返回-1/0/1之一,用于表现v1“小于/等于/大于”v2。

CompatLayer.js设计了一个checker对象,它的属性就是浏览器版本标识。例如“ie6、ie5”之类:

  checker = {
    ie6 : ie && document.compatMode,
    ie55 : ie && !this.ie6 && [].push,
    // ...
  }

而后面的表达式是检测该版本的代码。

四、Qomolangma中载入指定版本浏览器的兼容代码

如果checker.<versionTag>条件为真,则一段相应的兼容代码被自动载入。这段代码的文件名约定为common_<versionTag>.js

一个低版本的兼容代码只需要实现兼容更高版本浏览的代码,最后再载入高版本的兼容层即可。例如common_ie5.js未尾就载入了common_ie55.js,而后者再载入common_ie6.js。

这样的设计,使得ie6的环境中不需要载入任何“无关的(非必须的)”兼容代码。

五、兼容层代码的特点与问题

Qomo的兼容层充分地考虑了可扩展性。并为后续代码中使用这种载入机制提供了可能:在可以通过$import.get('browser')来取得浏览器版本标识“versionTag”。

Qomo兼容层在IE中实现到for IE5.0的兼容,但也付出了巨大的牺牲。这包括:

  • common_ie5.js巨大,且实现不完整
  • 不能使用<property> in <Object>语言来检测属性是否存在,因为IE5不支持该语法
  • 实现了IE5中的RegExp的$x属性和相关特性(例如支持函数的String.replace),但效率较差,因此IE5的unicode解码只能采用vbs_JoinBytes()。这也使common_ie6.js出现了冗余设计。

Qomo通过对IE5兼容的研究,提出了在Mozilla(firefox)、Opera、safari等浏览器中实现面向IE6提供JavaScript兼容特性的通用方法。例如在common_safari.js等中可以看到RegExp的兼容实现。

Qomo发现了一些与浏览器版本或内核引擎相关的严重BUG。例如只支持firefox 1.5.0.1以后版本,并非是Qomo采用了何种特殊的、非ECMA特性的JavaScript语法,而仅仅是因为此前的Firefox(以及采用早于Geckco 1.8.0.1的Mozilla系列引擎的浏览器)存在严重的BUG。

同样,在兼容层中的一些设计,例如common_mozold.js和common_safari.js中对execScript()的实现。也仅仅是一些未被修正的浏览器BUG导致的代码开销。

与此相同的,是在IE5上著名的RegExp.lastIndex BUG,这使得common_ie5.js只能用substr()去分隔字符串,以实现RegExp.replace()。——尽管这是低效的根源。

这些问题与Qomo使用怎样的兼容方案无关。因此我们只能寄期望于用户使用更新的、没有BUG的浏览器。——再或者,忍受bug修补代码所带来的效率开销。:(