新时代新潮流WebOS 【17】需要不需要JavaScript
Netscape于1994年成立,并迅速走红,1996年占据了浏览器 市场接近90%的市场份额。然而从1997年开始,在微软IE浏览器的强大攻势下,Netscape败走麦城,转眼到了2006年,Netscape的市 场份额只剩下1%不到。虽然Netscape昙花一现,但是短暂的辉煌,却留下了很多技术遗产,影响至今。
Brendan Eich 1986年毕业于UIUC,1995年入职Netscape,当时正值Netscape如日中天。入职后不久,Brendan Eich成功说服他的领导,开始了JavaScript的研究和开发,成为JavaScript之父。Brendan现在供职于Mozilla.com。
Brendan 在2008年8月23日的博客中写到,“我极其高兴地宣布,TraceMonkey正式投入使用。这是Firefox所使用的SpiderMonkey JavaScript engine的一次更新换代,TraceMonkey将植入在Firefox3.1版本,其亮点在于使用了一种新型的Just-In-Time(JIT) 编译器,使JavaScript的运行效率提高一个数量级以上。”
Figure 1. Benchmark comparison for JIT
图一显示了没有配备JIT的Firefox 3.0的SpiderMonkey JavaScript engine,与配备了JIT的TraceMonkey JavaScript engine的运行速度的比较。JavaScript包含的功能很多,处理各个功能的速度快慢不一。譬如,JavaScript engine处理Image object的速度非常快,但是处理数学运算例如matrix,通常会慢很多。Brendan团队用四种不同的测试用例集(benchmarks),多方 面测试和比较,配备了JIT与没有配备JIT的两种JavaScript engines的运行效率。结论是配备了JIT,能使JavaScript engine的速度提高一倍甚至更多。
Brendan的博客隐含三个问题,1. 为什么JavaScript engine的运行速度很重要?2. 为什么JIT能够使JavaScript engine运行速度大大加快?3. 为什么选择JIT技术,而不是预先编译的办法?
第一个问题很好回答,因为JavaScript的应用领域日益广泛,譬如Google Docs,试图用Ajax技术实现基于浏览器的在线的Office软件,与离线的Microsoft的Word,PPT,Excel甚至Visio等等产 品竞争。但是Microsoft Office软件包含的运算逻辑非常复杂,譬如绘图软件Visio包含了大量的计算几何运算。虽然JavaScript应付简单的动态网页游刃有余,但是 面对这些大计算量的应用,JavaScript engine的运行速度就至关重要。
第二个问题,相对于逐句解析,JIT的优势在于可以 整段地编译源代码,而且缓存编译的结果,这样就避免了重复解析那些先前已经解析过了的源代码。另外整段编译并缓存编译结果,有利于优化代码,通常包括 inline expansion, dead code elimination, constant propagation, loop transformation, register allocation以及automatic parallelization等等。当然,优化代码是需要时间的,优化程度越高,运行时效率越高,但是启动时间越长。
第三个问题,为什么不用预先编译的办法。所谓预先编译,指的是开发者预先编译,在网站上发布编译好的二进制代码,而不是源代码。当浏览器访问到相关网页时,随即下载这些编 译好的二进制代码,然后直接运行之,省略掉了JIT编译的过程,也回避了逐句解析运行效率低下的弊端。为什么不预先编译JavaScript源代码?这个 问题表面上看起来也很容易回答,因为Java Applet曾经尝试过预先编译的办法,但是Java Applet以失败告终。这段历史教训似乎说明了,网页不应该绑定编译过的二进制代码,而最好只绑定文本形式的源代码。
但是如果深究一下Java Applet失败的原因,会发现不应该把Java Applet失败的罪责推卸给预先编译。
关于Java Applet失败的原因,Wikipedia谈到了兼容性的历史纠葛。Java的卖点是write once, run anywhere,所以Sun公司非常重视Java的兼容性。可是微软推出IE浏览器早期版本的时候,使用的Java虚拟机是微软自己的产品,其中包含一 些非标准的Java功能。这样,在IE浏览器中能够运行的Java Applet,不一定能够在其它浏览器中顺利运行。Sun找微软谈判,让微软去掉这些不合标准的Java功能,未果,于是对簿法庭。官司一打就是几年,控 辩双方精疲力竭。最后,微软宣布不再开发微软自己的Java虚拟机,同时IE浏览器也不预装Sun版的Java虚拟机。很多普通用户不知道如何自行安装 Java,当他们用浏览器访问网站时,如果遇到包含Java Applet的网页,就必须当场下载并安装JRE,这是一个非常耗时的过程,网页迟迟不能显示出来,导致用户体验很差。
假设历史上没有发生微软和Sun关于Java版本之争,Java Applet会不会大行其道呢?未必。
Wikipedia 还提到另一个Java Applet失败的原因,初看起来感觉有点吹毛求疵,但是反复想想,觉得其中大有深意。Wikipedia说,以Netscape为代表的一些浏览器,在 处理占满整个浏览器页面的Java Applets的时候,效果不佳,但是JavaScript勉强还行。让Java Applet脱离原先的浏览器页面,弹出一个新的窗口,也不是一个好办法。因为这样做,原先的浏览器页面就无所事事,用户会不自觉地关闭它,但是不曾想, 母体关闭了,Java Applet新窗口也随之关闭。
网页弹出一个窗口,是大家经常遇到的情况。为什么司空见惯的 小错误,对于Java Applet来说,却是不可容忍的大错呢?原因在于这样的做法,导致浏览器仅仅成为用户接触Java Applet的通道,一旦Java Applet的窗口打开了,浏览器本身就显得无足轻重。值得关注的观点是,用户接受并且喜爱浏览器的界面,他们不欢迎Java Applet把浏览器矮化成通道的做法,他们尤其不欢迎Java Applet的UI风格看起来和浏览器的风格不一致。
这个观点隐含的核心命题是,Java Applet之所以失败,根本原因在于Java Applet谋图游离于浏览器母体之外,寻求独立的倾向。
解释Java Applet为什么失败的猜想很多,如果这种观点仅仅是众多猜想之一,倒也无足轻重。但是继Java Applet之后,Sun进一步延续和深化让Java游离于浏览器母体之外的路线,推出了Java Web Start(JWS),JWS是否成功,可以验证这个猜想是否正确。
Java Applet是内置于浏览器框架中的插件,运行Java Applet的Java虚拟机是浏览器的一个内部组件。微软的IE浏览器,对于Sun的Java虚拟机,采取非暴力不合作的抵制态度。这个局面导致Sun 认定,欲大力推广Java Applet,必先绕开浏览器的制肘。在这种想法驱使下,Sun推出了Java Web Start(JWS)技术,以此替代先前的Java Applet。Java Web Start把浏览器彻底变成通道,一旦打开JWS,用户看到的不仅仅是弹出的另一个窗口,而且这个新窗口和浏览器母体没有紧密的联系。不仅JWS的UI渲 染完全可以用Java Swing实现,而且鼠标点击,敲键盘等等一系列事件的捕捉和响应,也完全由Java Swing控制,和浏览器无关。
与Java Applet的命运类似,JWS也没有大行其道。批评者认为,失败的根源在于JWS的设计理念有缺陷,
1. JWS的UI渲染风格与浏览器不同,同时,JWS渲染UI的速度也比浏览器慢,原因是浏览器直接调用图形函数库,而不需要像Java那样,需要经过JVM的转手。
关于这一点,笔者多少有点疑虑,JVM的确会造成运行效率的降低,但是浏览器需要解析HTML和CSS,这个过程同样也会耗费时间。有没有 benchmarks,比较一下渲染相同的页面,JWS和浏览器各需要多长时间?但是话又说回来,即便JWS与浏览器在UI渲染的速度方面不相上下,也没 有充足的理由让JWS重复浏览器的工作,尤其是当浏览器已经做得足够好,并且已经被用户广泛接受。
2. JWS游离于浏览器之外,把浏览器仅仅作为HTTP数据传输的通道,这样的做法隐含着IPC的overhead。原因是,浏览器和JWS运行在两个不同的 进程,浏览器与JWS之间的数据传输不可避免地依赖于Inter Process Communication (IPC),而IPC也影响运行效率。
详细说来,JWS和浏览器这两个进程之间交换数据,是通过message passing来实现的。在两个不同进程之间传递数据的过程,原理上很简单,sender进程把需要传输的数据,逐段复制并存入CPU的register 缓存空间,然后receiver进程从CPU的register读取数据。但是假如CPU正在处理其它工作,那么在执行IPC之前,首先要暂时中止当前的 工作,等待IPC结束后,再继续进行这份工作。在CPU中止当前的工作的时候,需要首先把CPU register空间里,当前正在处理的数据(switchframe)复制一份,缓存起来。当receiver读取完数据以后,再把缓存起来的数据 (switchframe),重新移入CPU register。显而易见,把CPU register里的数据复制移出,然后再重新移入,是需要消耗资源的,这就是所谓不同进程之间的IPC,所引发的context switch的代价。
3. 对于JWS的批评,并不是针对Java本身。相反,批评者们认为,Java和浏览器是天生班配的一对,应该取长补短,相得益彰,而不是试图取代对方。说得具体点,
a. 让浏览器专注于UI的渲染工作,以及鼠标点击和键盘输入等等事件的捕捉。
b. 让Java专注于数据逻辑的处理。
c. 系统架构方面,不应该以渲染机为中心,把Java作为插件。应该让Java承担起浏览器的整体控制工作,而渲染机作为浏览器的一个模块,负责UI绘制和事件捕捉。
d. 丰富Java对于HTML DOM object的处理功能,换而言之,所有今日JavaScript有关HTML DOM的APIs,Java都必须实现。
4. Sun Microsystem不应该因为商务上的受挫,就愤然让Java背离浏览器。Java与浏览器,或者更准确地讲,Java与渲染机,合则两利,分则两伤。HotJava浏览器本来有远大前途,raner浅尝辄止,实在可惜。
Figure 2. Browser War I and II
回到标题的话题,有一种观点认为,需要不需要JavaScript, 取决于有没有更好的替代品出现。JavaScript的原罪在于它是一个文本格式的脚本语言,对于文本格式的脚本语言,要么当场逐句解析,要么当场JIT 编译,无论如何,性能都不会超越预先编译。JavaScript的兴起,在于Java Applet的没落。Java Applet之所以没落,是因为没有与HTML-DOM紧密关联。JavaScript之所以成功,并不在于这个语言的内核有多少优势,而是在于它提供了 简洁方便的操控HTML-DOM的APIs。如果对于Java Applet加以改造,丰富其操控HTML-DOM的APIs,则Java Applet不一定没落,因之,JavaScript也未必会兴起。
从这个观点出发,浏览器需要重构。与第一代和第二代浏览器不同,新一代浏览器不再以WebKit之类渲染机为中心,而是以Java-DOM操控为中心,渲染机为专项功能。在新一代浏览器中,本质上不需要JavaScript的存在,除非需要反向兼容老旧的含有JS的网页。
所谓第一代和第二代浏览器,并没有严格划分。1989年,欧洲原子能研究组织(CERN)的一名合同工,牛津大学的毕业生Tim Berners-Lee,提出了用HTTP协议构筑万维网(World Wide Web)的设想。1993年,美国国立超级计算应用中心(NCSA)开发了Mosaic浏览器,并被广为接受,从而拉开了第一次浏览器大战的序幕。继承 Mosaic的衣钵而发展起来的Netscape浏览器,曾经辉煌一时,1996年占据了90%左右的市场份额。可惜好景不长,1995年微软决定开发 IE浏览器,到了1997年,IE的竞争势头已经锐不可挡,到了2003年,Netscape大势已去,第一次浏览器大战就此结束,以微软IE浏览器完胜 而告终。Mosaic,Netscape以及IE的6.0以前诸版本,还有史前的其它浏览器,通常被称为第一代浏览器。
自2003年以后,Mozilla Firefox,Apple Safari,Opera,以及2008年问世的Google Chrome,纷纷挑战微软IE,蚕食其市场份额。虽然从技术上讲,这些新兴的浏览器的确比IE有一些进步,但是技术上的局部优势,并没有大规模地吸引 IE的用户。
前面提到的观点,可以说是浏览器领域激进派的看法。他们认为,小修小补颠覆不了IE的霸主地位。根本的 解决办法,是重构浏览器,大幅度提高浏览器的性能,使之能够从容地处理更加复杂的计算逻辑,譬如Visio那样绘图功能,或者大计算量的网络游戏。只有这 样,才能争夺那些习惯于使用IE的用户。
笔者认为,PC上的浏览器,历史包袱沉重,要冲击现有浏览器的架构设计,估计困难重重。但是手机浏览器还处于诸侯混战之中,没有太多历史积淀。或许这是一个轻装上阵,试一试新一代浏览器的良好突破口。