对 ES4 的第一轮尝试

自首次 TC39 会议上 Borland [1996] 提出在语言中添加类(class)定义的提案起,人们一直希望尝试在 JavaScript 中添加新特性,以便应对大型程序的复杂性。Netscape 的 JavaScript 1.2 支持运行加密签名后的脚本,它们可以通过 importexport 声明 [Netscape 1997a] 相互集成。微软的 JScript 3 则包含了条件编译特性 [Clinick 1997]。1998 年 2 月版的《ECMAScript 展望一览表》[TC39 1998c] 将「包(package)概念」列为了 V2 的可选项。这类用于支持大型项目开发的特性较早从 ES3 特性集中移除,但 TC39 仍然在并行地进行着这些工作。

与其相关的首个重要提案来自 Dave Raggett,他是由惠普赞助的 W3C 研究员。当时 Raggett 正在 W3C 开发一项名为「Spice」的提案,以改善 HTML、CSS 和 JavaScript 的集成。这份提案的早期版本 [Raggett 1998c] 于 1998 年 2 月提交给了 TC39。除了与 HTML 和 CSS 相集成的特性外,Raggett 的初始提案中还包括了一种用于声明原型对象的构想。这种构想与 Borland 的类声明提案相似,增加了用于将事件处理器与原型对象声明式关联的能力。这份提案还包括了用于定义「库」(library)和从库中导入各种定义的设计,如下所示:

  1. // 1998 年 2 月的 Spice 提案
  2. import document, block, Inline from "http://www.w3.org/Style/std.lib";
  3. prototype Link extends Inline
  4. {
  5. href = "http://www.w3.org/";
  6. when onmousedown
  7. {
  8. document.load(this.href);
  9. }
  10. }

根据 1998 年 3 月的会议记录 [TC39 1998d],当时会上讨论了 Dave Raggett 最初提交的 Spice。记录中指出对其的「初始反馈是负面的」。于是 Raggett 与来自 HP Labs 的两位语言设计师 Chris Dollin 和 Steve Leach 一起继续发展他的提案。在 9 月,Raggett 提交了一组新的文档 [Raggett et al. 1998] 来描述扩展后的 Spice 提案。实际上,这个提案是一门不兼容 ECMAScript 的替代性语言,甚至用基于闭合关键字的语句语法替代了花括号分隔的 C 式语法。

在 1998 年 11 月,Spice 的设计者与来自 Netscape 和微软的 TC39 代表之间举行了一次私人会议。而后在当月的 TC39 工作组会议上,Dave Raggett [1998a] 介绍了经过修订的 Spice 提案。工作组会议上,虽然 TC39 成员对于「替换当时的语句语法」或「立即尝试集成对 CSS 的声明式支持」都缺乏兴趣,但他们有兴趣用 Spice 提案中的某些概念来扩展 ECMAScript,例如类、数值单位50、类型和模块机制等。Raggett 在讨论中指出,一旦类似特性添加到了 ECMAScript 中,惠普就不太可能继续开发 Spice51

为了制定出一份能在 1999 年 1 月提交给整个 TC39 的的提案,委员会成立了一个新的 TC39 Spice 工作组。委员会认为,必须使用已经保留的 Java 关键字来定义支持新核心概念的新特性,并且类的语义应该与 Java 类似。各种数值单位应该基于类来定义,这需要增加对运算符重载的支持。

Spice 工作组的首次电话会议在 1998 年 12 月的第一周举行。12 月 10 日,Dave Raggett [1998b] 在会议的基础上分发了一份新文档,它主要涉及包和数字单元,但还更广泛地探讨了包括类和接口在内的类型声明。它的重点更多是语法而非语义。这份文档的设计基于 名义化类型系统g,包括如下:名义化的内置基本类型、同质(homogeneous)的数组类型、类(class)类型(其中的子类均为 nominal subtype)、接口(interface)类型,以及表示需要进行动态类型检查的 any 类型。在语法上,它探索了将类型与变量绑定相关联的新方法。文档仍然假定使用 var 关键字用于变量声明,并探讨了两种类型注解的风格。其中一种 C 式的风格将类型表达式作为变量名声明的前缀,而另一种 Pascal 式的风格则在变量名声明后书写冒号和类型表达式。图 24 中举例说明了这两种替代方案。

  1. // C 风格的声明可选形式
  2. var float x, int[] y, z; // z 的类型是什么?
  3. var float x, int[] y, int[] z; // 是这样吗?
  4. var float x, int[] y, any z; // 或者是这样吗?
  5. // Pascal 风格的可选形式
  6. var x: float, y: int[], z; // z 的类型是 any

图 24. 在初版 ES4 中,基于 C 和 Pascal 语法所衍生出的几种类型注解的可选形式。

对类和接口所定义的语法大致遵循 Java,包括了对 publicprivateprotected 和默认(包级)可见性修饰符的完整补充。语言底层的元对象结构未涵盖在内,但这里的元对象模型已经必须与当时的 JavaScript 原型继承模型存在隐式区别了。这份文档提出了关于如何区分「使用声明出的静态类型信息的早期绑定(early binding)成员访问」和「没有静态类型信息的延迟绑定(late binding)成员访问」的问题。文档还探索了属性的动态添加52,认为可以在类中禁用这一能力。

相关的设计讨论发生在 1999 年 1 月和 2 月 [Raggeet 1999b, c],主要与类、类型注解g和作用域有关。Chris Dollin、Waldemar Horwat 和 Herman Venter 是首要的参与者。许多讨论都涉及用类所定义出的对象的性质,以及类成员访问的语义。Dollin 和 Venter 主要倾向于使用类似 Java 的语义,其中类实例的结构由类声明静态确定,并且成员的可访问性都能基于类型信息静态地决定,可以在文档网站上查找到。Horwat 则主要倾向于使用更具动态性的模型,其中即使存在类型注解,也会使用不可靠的动态查找来访问成员。要想满足现有 JavaScript 程序员的预期,并兼容那些使用了「基于原型的特设类(ad hoc class)」的已有代码,似乎都需要动态语义。这里涉及的特性包括可选的类型注解,以及 expando propertiesg。此外 Horwat 认为,动态语义与脚本的性质更加一致,脚本编程天生地涉及从多个来源动态组装出代码,并使用独立于引用方脚本做版本控制的库。Horwat [1999b] 在描述成员查找可选方案的文档中,总结了使用静态方案和动态方案之间的区别。

在 2 月的会议上,Waldemar Horwat [1999a] 展示了他的「JavaScript 2.0」规范。他说这是一份最早为 Netscape 编写的实验性设计53,但与 TC39 最近讨论的内容相匹配54。规范中包含了具有大量机器级数字类型的名义化类型系统,类似 Java 的类成员可见性规则,以及带有显式 import 支持的包。它还具有许多更新颖的特性,包括:类扩展声明、包成员的声明级版本控制、nullable 和 non-nullable 的类型,以及一等公民的类型值。JavaScript 2.0 提出了一种「流式执行模型」[Raggett 1999d],取代了之前 JavaScript 版本的声明提升(declaration-hoisting)语义。在这种语义下,声明要在执行过程中遇到时才会被处理。例如 if 语句可以用来选择性地声明变量,或者用来选择带有不同类型注解的声明。像这样「一等公民类型值」和「声明的流式执行」的结合,会使得在某些情况下无法进行完整的静态类型检查。

JavaScript 2.0 并未尝试与原始 JavaScript(甚至还有尚未完成的 ECMAScript 3)完全向后兼容。在向 TC39 介绍 JavaScript 2.0 时,Waldemar Horwat 表示:「至少,你应该能写出在 ECMAScript 1.0 和 2.0(ES4)中都能工作的代码。完全向后兼容会非常痛苦 [Raggett 1999c]。」例如可选类型注解所带来的语法复杂性,会导致难以在换行符上支持自动分号插入。Horwat 对向后兼容性的解决方案是在实现中提供多个编译器。他认为根据语言版本切换编译器,要比使用具有严格前向兼容性的单一语言更可取。

在 1999 年剩下的时间中,TC39 的大部分注意力都集中在完成 ES3 上。但是在 3 月,它发布了《未来特性展望表》[TC39 1999c],介绍了可能在 ES3 之后发布的特性。Spice 工作组则转为了一个模块化子组,并继续时常举行关于初版 ES4 的会议 [Raggett 1999a, d; TC39 1999a]。到 11 月,TC39 已经将主要注意力转移到了「第 4 版」上,并更新了 ES3 之后的未来特性展望表(见图 25),于是这一步伐加快了。TC39 主席 [Lewis 1999a] 在 1999 年 11 月的报告中描述了初版 ES4 的目标:

ECMAScript 2.0 是一个进取且大幅改进的 ECMAScript 语言规范,委员会希望在 2000 年实现标准化(当然这个野心可能过大)。ECMAScript 2.0 的主要目标是为「大规模编程」提供支持——这也就是说,要支持开发由多人构建的程序,并可能是史上第一次要在用户的桌面上组装出这些程序。

  1. * 模块化增强: classes, types, modules, libraries, packages
  2. * 国际化(I18N)相关:
  3. - 国际化库 [可能作为独立的 ECMA 技术报告]
  4. - 日历
  5. * 十进制小数(对 Number 对象的增强或替代)
  6. * Catch guards(带类型)
  7. * 对原子(线程安全)操作的讨论或定义(可能非规范性)
  8. * 其他各类更新 [除模块化相关外]:
  9. - 声明修饰符的扩展机制
  10. - 可扩展的语法(如用 # 表达 RGB 值)
  11. - 单位语法和算术库
  12. - Here 文档(长字符串常量)

图 25. TC39 在 1999 年 11 月的《未来特性展望表》[TC39 1999d] 中设想的初版 ES4 特性。

在 2000 年 1 月的会议上 [Raggett 2000],微软砍掉了一些特性,希望能赶在 2000 年 12 月发布规范的第四版。微软的主要兴趣是添加静态类型注解并保持向后兼容性,包括对自动分号插入的支持。Venter 介绍了对 ES3 规范的一系列更改,他认为这些更改足以支持类型注解。但此时在类型系统性质方面仍然存在很多不确定性,包括:类的语义、包的语义、命名空间的语义,以及如何将静态和动态的语言概念集成到单个语言中。

在 2000 年 6 月 22 日,微软 [2000b] 发布了 .NET Framework,这是微软为应对 Sun 在 Java 平台上的竞争所做出的回应。微软 .NET 是一个多语言的应用开发平台,除了主要语言 C# 外,它还支持 Visual Basic 和 JavaScript 等其他语言的方言。消息发布后,第一个 .NET 预览版55在 7 月的微软专业开发者大会上发布 [Microsoft 2000a]。预览版包含了早期版本的 JScript .NET [Clinick 2000]。与浏览器中的 JavaScript 不同,JScript .NET 是一门面向 .NET 公共语言运行时(CLR)的预编译语言,其内部使用了 .NET 的类型系统。Internet Explorer 并不支持 JScript .NET(或通称为 .NET)。相反地,JScript .NET 一开始就可以使用各种 .NET 框架组件来构建桌面、服务端和命令行应用。JScript .NET 宣称自己与 ES3 规范兼容,但由于其设计目标并非运行为浏览器编写的 JavaScript 代码,因此它并不需要严格的向后兼容性。除了 ES3 特性外,JScript .NET 还添加了可选的静态类型注解、包含成员可见性属性的类和接口声明,以及支持显式 import 的包。根据微软 Andrew Clinick [2000] 的说法,这些新特性是与其他 Ecma TC39 成员一起设计的。他还告诫说,根据正在进行的 TC39 讨论,设计的细节可能会改变。

在 .NET 于 2000 年 6 月发布前,微软的 Herman Venter 并不能与 Waldemar Horwat 或其他 TC39 成员讨论 .NET 或 JScript .NET。当年 8 月,Horwat 和 Venter 私下见面,试图就完成 ES4 标准达成足够的一致。Horwat [2000] 在会议笔记上记录了对 43 个问题与分歧的讨论,其中总结了以下讨论:

概括说来,Herman 正在为服务端准备 JScript 的实现,并希望冻结这门语言,使其易于与微软 .NET 运行时互操作。Waldemar 担心这门语言对浏览器的适用性,并希望保留语言的动态性。他认为这是 ECMAScript 的与众不同之处。Waldemar 担心语言向 Java 或 C# 发展,因为他认为在这两者擅长的领域里,几乎不需要另一种新语言。而且对于静态编程来说,新语言最终能获得的结果也比不上 C# 语言。Herman 还建议在新的服务端项目上使用 C# 而非 JScript,并将新的 JScript 视作一种针对「已经习惯使用 JScript 编程的开发者」的语言。

Horwat [2003a] 从 JavaScript 2.0 文档中 fork 出了一份单独的《ECMAScript 4 Netscape 提案》文档,然后将这份文档用作正在进行的初版 ES4 开发的工作草案。JavaScript 2.0 文档则继续并行维护,包括了 TC39 尚未同意加入的其他特性。

微软希望 .NET 及其语言能被视为标准化的技术。Ecma 组织在这方面有口皆碑,可以很容易地将专有技术转移到标准轨道上,并且微软对 TC39 的工作方式也感到满意。因此,微软向 Ecma 建议扩展 TC39 的职责范围,并在其中将 .NET 标准化。于是 TC39 被重新定义为 Ecma 面向「编程环境」的技术委员会,正在进行的 ECMAScript 开发活动在 TC39 中被降级到了 TG 任务组(Task Group)状态,称为 TC39-TG1。Ecma 成立了其他的 TC39 任务组,以开发 CLR 和 C# 的标准。

这次创建 ECMAScript 规范第四版的尝试还会再进行三年。但事后看来,JScript .NET 的发布已经敲响了这项工作的「丧钟」。到 2000 年 6 月,Netscape 已经输掉了「浏览器大战」[Borland 2003],其浏览器市场份额下降到了 14% 以下 [Reuters 2000]。在被美国在线公司收购后,它正逐渐失去人手,被迫以更少的资源运营,只能艰难地继续更新其浏览器。

微软凭借 IE 在竞争中取胜,并最终获得了 90% 以上的市场份额。它对继续增强自己没有专有控制权的 Web 编程平台的兴趣不大。在微软内部,研发资源从增强开放的浏览器技术(例如 ECMAScript)转到了开发专有的微软技术(如 Windows Presentation Framework56 [Microsoft 2016])上,微软希望它们能最终淘汰和取代开放的 Web 技术。在 .NET 的编程语言领域,微软专注于 C# 和 VisualBasic .NET。在这种情况下 JScript .NET 的重要性,仅仅取决于它能使多少 JavaScript 程序员迁移到 .NET 平台。

TG1 继续开展会议,讨论特定问题,并更新规范草案57。微软与 Netscape 在类型系统的性质方面存在着重大的持续分歧。Waldemar Horwat 在 MIT 轻量语言研讨会上发表了有关 JavaScript 2.0 设计的论文 [Horwat 2001],他将 JavaScript 2.0 描述为具有「强大的动态类型」能力的语言。他进一步解释说,在 JavaScript 2.0 中,所有变量都具有关联的类型,这些类型限制了可以存储在其中的值,但是类型约束的检查必须在运行时进行。在一般情况下,JavaScript 2.0 的一等公民类型值和隐式的 downcast58 会导致无法对程序进行静态类型检查。

TG1 召开会议的频率和出席人数都在逐渐降低。Chris Dollin 于 2001 年 6 月参加了最后一次会议,而 Herman Venter 参加的最后一次 TC39-TG1 会议则是在 2002 年 6 月。2003 年 7 月 15 日,美国在线宣布将解散 Netscape,并解雇了包括 Waldemar Horwat 在内的大多数员工。在同一周举行的 TG1 会议上,Horwat 辞去了 ES4 编辑的职务。TG1 的其余成员决定将精力集中在为 ECMAScript 开发 XML 支持上,并中止 ES4 的工作,直到 XML 项目完成并可以确定出新的编辑为止。