JavaScript的元系统
本文是对在第四届FEDay中分享的《无类继承:JavaScript面向对象的根基》的进一步讨论。也是对开源项目@aimingoo/metameta的解析。
本文是一个系列,包括:
以及相关下载:
- 资源分享:第四届FEDay讲演主题 - 在这里
ECMAScript中只有两处提及到“Meta”这个概念,一处是说明ECMAScript的规范类型(a specification type)是用于描述和实现语言类型(language types)的元值(meta-values),另一处则是唯一被称为“元属性(Meta Property)”的new.target
。
所以ECMAScript中是没有所谓“元系统(Meta system)”或“元类型系统(Meta type system)”。我们在这里先定义一个称为“原子(Atom)”的东西,并基于此来构建起一个完整的JavaScript元系统。
原子(atom)
定义:原子是JavaScript中的对象的最小单元,它是对象但不继承自Object();以原子为原型的对象也会被称为原子对象。
JavaScript中的对象就是一个属性包(properties bag, or a collection of properties),一个属性包为空集时,它必然是对象的最小形态。因此一个没有原型,且自有属性集为空的对象,必然是一个原子。
原子可以用ES5兼容的语法创建出来:
var atom = Object.create(null);
也可以通过将一般对象的原型置为null来得到一个原子:
var atom = Object.setPrototypeOf(new Object, null);
并且,在ECMAScript中有三个内建/原生对象是原子的:
function isAtom(x) {
switch (typeof x) {
case 'object':
case 'function': return !(x instanceof Object);
}
return false;
}
// modules in es6
import * as namespace from './your-module.js';
console.log(isAtom(null));
console.log(isAtom(Object.prototype));
console.log(isAtom(namespace));
在同一个运行环境中,可以并存多个原子,以及由原型指向原子的、原型继承的对象系统。所有这些原子以及衍生的对象系统都是互不相等、没有交集的。
> Object.create(null) === Object.create(null)
false
因此,JavaScript原生的、由Object()派生或创建的对象、类,在本质上也是上述“对象系统”之一。但是,
- 作为唯一特例,Object()所属的对象系统称为“原生对象系统”,以区别于后来创建的其它原子对象系统。
并且,
- 作为唯一特例,null值是一个原子(注:原子在ECMAScript约定的ECMAScript language types中不是对象,但在JavaScript自身的类型检查(typeof)中它是对象)。
NOTE(2018.08.28): 修正了一处关于arguments的错误,确认arguments对象不是原子。thanks for hebaby @github
元(meta)
定义:能产生原子(atom)的一个过程称为元(meta)。
推论:原子的构造器(Atom)与元(meta)是等义的。
由于atom对象的构造器通常记为Atom(),所以从概念上它与“元(meta)”是等义的,在实际使用中我们也并不明确地区分二者。
meta可以是一个函数,也可以是一个类,甚至也可以是一个代理对象(proxy)、箭头函数(arrow functions)或方法(methods)。——在概念定义中,我们只约定了“meta是一个过程”,并没有强调atom是它构建出来的,亦或只是它的调用结果。
在开源项目中Metameta(@aimingoo/metameta)中,meta是以ES6的语法声明的一个Atom类:
class Atom extends null {
constructor() {
return Object.create(new.target.prototype);
}
}
任何情况下,我们用该meta都可以产生新的原子对象:
> isAtom(new Atom)
true
> new Atom === new Atom
false
元类型(Meta,Meta types)
定义:所有元(meta)的类型称为元类型(Meta types)
在JavaScript中,一个数据所对应的类型可以用它的构造器来标示,亦即是Meta();并且这也意味着Meta()作为构造器产生的实例是元(meta)。亦即是说,Meta()应当是一个“返回meta过程”的过程。
在ES6的语法中,可以简单地在函数中返回一个“类声明(class definitions)”来得到一个字面量风格的类。因此在Metameta中声明了MetaMeta()类来作为元类型的祖先类:
// Meta's super
class MetaMeta extends null {
constructor(base=Atom) { // Atom() by default, NOTE: 声明在上例
return Object.setPrototypeOf(class extends new.target {}, base);
}
...
NOTE(2018.09.09): 修正了一处关于MetaMeta()类的错误,这是因为在本文中为了简化Meta()类的描述而直接暴露了MetaMeta的实现,但又没有处理相关的逻辑所导致的。需要注意的是,在Metameta项目中该类的实现与本文是略有区别的。thanks for nextdoorUncleLiu @github
所以现在,我们就可以通过如下的方法来得到一个原子了:
// Atom与meta是同义的
> Atom2 = meta = new MetaMeta
// 创建一个原子
> atom = new Atom2
// 检测
> isAtom(atom)
true
基于原子的继承性
我们之所以要用class来声明Atom和MetaMeta,是为了简单地得到面向对象的继承性。亦即是说,当我们想要派生一个新的原子对象类型的时候,可以简单地通过扩展上述的系统来得到它的构造器。例如:
class MyAtomObject extends new MetaMeta {
get description() {
return 'i am an atom.';
}
}
var x = new MyAtomObject;
console.log(x.description);
在这个例子中,new MetaMeta
直接创建了一个Atom,而MyAtomObject
则派生自该Atom,因此它的实例自然是atom。并且,基于ES6的类声明语法,MyAtomObject
也可以具有自己的存取器成员、对象方法,或者类方法。
基于元的继承性
从MetaMeta也可以基于元类型进行派生,由此我们可以实现“元类(Meta class)类型”。
定义:元类(Meta class)是一个产生类(class)的过程。
从定义上来说,简单的元类可以写成:
function SimpleMetaClass() {
return class {};
}
当然,由于在MetaMeta中“元类型”本身就是基于类实现的——亦即是它本来就是一个“返回类”的过程,因此它只需要简单的一层概念抽象就可以实现“元类”类型了。如下:
// “元(Meta)”类型
class Meta extends MetaMeta { ... }
// “元类(MetaClass)”类型
class MetaClass extends Meta { ... }
之所以让Meta派生自MetaMeta(),主要目的是为了得到一层super声明,以确保Meta()以及它的类方法(static methods)之于它的super是词法上下文绑定的。而“元类(MetaClass)”则用于派生一层类型声明,以便让MetaClass()能拥有自己的类方法,例如MetaClass.isClassOf()
。
现在,我们已经在Meta上实现了一层派生,我们也可以实现更多层的派生,以通过“类类型”的方法来得到更多的构造器——换言之,我们可以产生更多的类,它们都可以作为更多的“不同的对象系统的”祖先类。我们可以让JavaScript中出现多个完全不同的、与Object()所代表的“原生对象系统”并列的对象系统。
如前所述的——它们相互独立,没有交集。例如:
// “元类(MetaClass)”产生类
var ObjectEx = new MetaClass;
// 基于ObjectEx可以派生一个“独立的、不同的”对象系统
class MyObjectEx extends ObjectEx {};
// 可以用类似的方法来派生更多这样的对象系统
class MyObjectPlus extends new MetaClass {
...
};
接下来,你可以检测它们的类属关系:
> ObjectEx.isClassOf(MyObjectEx)
true
> MetaClass.isClassOf(ObjectEx)
true
或使用ECMAScript内置方法检测原子:
> (new MyObjectEx) instanceof ObjectEx
true
> (new MyObjectEx) instanceof MyObjectPlus
false