待整理
Realms、Jobs、Proxies 和元对象编程(MOP)§
Harmony 的目标之一,在于使异质对象不分其为内置还是由宿主定义,均能实现自托管,并完全确定其由 Web 浏览器所实现的语义扩展机制。为支持这一目标,需要完善某些 ECMAScript「虚拟机」中现有的抽象,并进一步增加新的抽象,以确定新的(或不够明确的)语言特性。
「Realm」[Wirfs-Brock 2015a, pg. 72] 是一种新的规范抽象。引入它的目的,是为了支持在单个 ECMAScript 执行环境中描述多个全局命名空间的语义。Realm 能支持 HTML 页框的语义,这是 ECMAScript 自 ES1 以来一直忽略的浏览器特性。而「Job」[Wirfs-Brock 2015a, pg. 76] 这种规范抽象的加入,是为了确定性地定义 ECMAScript 执行环境该如何将多个脚本依次执行到完成(run-to-completion)。基于 Job 所提供的方法,能解释由浏览器和其他 JavaScript 宿主所提供的「事件派发」和「延迟回调」的语义。它们还为定义 ES2015 中 Promise 的语义建立了基础。
ES1 所提供的内部方法,基本上是个残缺的元对象协议。在对各种内置对象和宿主提供的对象做属性访问时,会有各类可见的语义区别。基于内部方法,可以将这些区别解释为它们在内部方法规范上的差异。但在 ES2015 之前,内部方法的语义还不够完整和规范,其使用也不够一致。为了「驯服」宿主对象,实现异质对象的自托管,并支持对象能力的隔离层g [Van Cutsem and Miller 2013]。ES1 到 ES5 中所设计的内部方法,被转换成了一种明确的元对象编程(MOP)。
JavaScript 代码要想定义异质对象,就必须能为这些对象所用的内部方法提供相应的实现。这个特性是由 ES2015 中的 Proxy 对象 [Wirfs-Brock 2015a, pg. 495] 提供的。新版 ES4 提出了一种名为「catchalls」[TC39 ES4 2006a] 的机制,从而让 JavaScript 代码能逐对象地覆盖当「试图访问某个属性,或调用某个不存在的方法」时发生的默认动作。这个「catchalls」机制的目的,是改进 JavaScript 1.5 的非标准 __noSuchMethod__ 机制 [Mozilla 2008a]。在 Harmony 中,Brendan Eich [2009b; 2009d] 引入了所谓的「动作方法」(action method)概念,使其能动态附加到对象上,从而令新版 ES4 的 catchalls 更进一步通用化。在对某个对象执行某些语言操作时,如果该对象上已定义了相应的动作方法,则会调用该方法。可用的动作集与 ES5 的内部方法集类似,但不是它们的直接映射。这里有个悬而未决的问题,即这些动作是在执行所有属性访问时触发,还是仅当访问不存在的属性时触发。Eich 所设计的用于将动作附加到对象上的 API,是以 ES5 对象反射函数为基础的:
关于如何使用代理
在直接代理的设计中,使用了一个封装过的目标对象。但它的设计目的并非提供目标对象的简易透明封装。与其表象相反,代理并不是一种用来记录属性访问或处理「方法未找到」问题的简单方式。为了支持这些用例而朴素实现的 Proxy 对象,通常是不可靠或有错误的。直接代理的核心使用场景,是对象的虚拟化和安全隔离层的创建。正如 Mark Miller [2018] 所解释的那样:
Proxy 和 WeakMap 的最初设计动机,是支持隔离层的创建。单独使用的 proxy 不可能是透明的,也不能合理地达到接近透明的程度。隔离层能合理且几乎透明地模拟 realm 的边界。对于具备私有成员的类而言,这种模拟基本上是完美的。
关于类定义的抽象问题
Mark Miller [2008d] 认为,对于类抽象所需的大部分运行时机制,在 ES3 中已经基于 lambda 函数和词法捕获技术实现了。词法捕获技术类似于 Scheme [Dickey 1992; Sussman and Steele Jr 1975],且由 Douglas Crockford [2008b, pages 52-55] 为适应 JavaScript 而进行了修改。这种「lambda 去糖化」的类定义风格,与模块模式实质上是一致的。它表明类只是一个小而轻的模块,其目的就是用来被多次实例化。Miller 称这种方法为「糖式类」(classes as sugar)。
Allen Wirfs-Brock 的提案还展示了对于扩展对象字面量的语法,该如何将其用作类定义的主体。在 2011 年 3 月的 TC39 演讲中 Wirfs-Brock [2011a] 提出,类定义应该能生成 ECMAScript 规范第 15 条94里内置库 Class 所使用的「构造函数、原型对象和实例对象」基本三要素,这在所有 ECMA-262 已有版本中都是通用的。与其将类定义去糖化为 lambda 表达式(糖化类)或一种新的运行时实体(受 Java 启发的类),不如将其去糖化为 JavaScript 程序员和框架作者们已经使用且熟悉的构造函数和原型继承对象。在会议上,大家对扩展对象字面量语法的许多细节有很大的意见分歧,但达成了一个宽松的共识,即核心类定义的语义,应该符合规范第 15 条中的构造函数、原型、实例三要素。
2011 年 5 月初,TC39 的 ES.next 特性冻结会议迅速临近,此时仍然有几个与类相关的稻草人提案在进行竞争。看起来委员会仍然未必有足够的共识,能使其中的某个提案被采纳。2011 年 5 月 10 日,Allen Wirfs-Brock 与 Mark Miller、Peter Hallam 和 Bob Nystrom 见了面。Hallam 和 Nystrom 是使用 Google 的 Traceur 转译器 [Traceur Project 2011b],对 JavaScript 类支持进行原型设计的团队成员。他们的原型融合了 Wirfs-Brock 和 Miller 提案中的想法。会议的目标是取得足够的一致意见,以便能提出一份统一的提案。Bob Nystrom [2011] 在其会议报告中列出了许多一致意见,包括:
……构造函数、原型和实例这三要素,足以解决其他语言中的类所要解决的问题。Harmony 类语法的目的,并不是去要改变这些语义。相反地,它是要为这些语义提供一种简明而声明式的外表,以便体现程序员的意图,而非底层的命令式机制。 ……对象是声明式和信息性的,函数则是命令式和行为式的。类的问题在于:「我们是否应将其建立在这些抽象的基础上。如果是的话,应该选择哪一个?」…… 在我们的共识提案中,会通过结合这两种手段来解决这种宗教式的分歧:引入一种类似对象字面量的形式作为类体,再加上一个函数来作为构造器。
