4.2.1 对象
尽管 ECMAScript 包含了定义类的语法,但 ECMAScript 对象也不是基于类的,像 C++、Smalltalk 或者 Java。相反,可以以各种方式创建对象,包括通过文本符号或通过构造函数(constructors)创建对象,然后执行通过向其属性分配初始值来初始化它们的全部或部分的代码。每个构造函数中都有一个名为 "prototype" 的属性,它的作用是实现基于原型的继承(prototype-based inheritance)和共享属性(shared properties)。对象是通过在构造函数上使用 new 表达式创建的。例如,new Date(2009,11) 就创建了一个新的 Date 对象。如果不使用 new 调用构造函数就会有产生其他的结果。例如,Date() 生成的当前日期和时间是以字符串表示,而不是一个对象。
每个通过构造函数所创建的对象都有一个隐式引用(称为对象的原型),它的值就是构造函数的 "prototype" 属性。进一步讲,一个原型可能会有一个非空的隐式引用指向到它的原型上,如此向上引用(and so on);这被称为原型链(prototype chain)。当一个对象中的属性被引用时,该引用指向包含该名称的属性的原型链中的第一个对象中该名称的属性。换句话说,第一个直接使用该属性的对象会检查它本身是否有该属性,如果该对象包含这个属性名,那么该属性将会得到引用;如果该对象不包含这个属性名,那么该对象则会通过该对象的原型(prototype)进行查找,如此逐级向上进行查找。
图1:对象 - 原型关系图
一般来说,在基于类的面向对象语言中,通常状态由实例承载,方法由类承载,继承仅具有结构和行为。 但在 ECMAScript 中,状态和方法都是由对象所承载的,而结构、行为和状态都是继承而来的。
所有对象都不直接包含一个特定的属性,而是在它们的原型上包含共享的属性和值。图 1 就说明了这个道理:
CF 是一个构造函数(同时也是一个对象),下面的 5 个对象实例都是通过 new 表达式创建的:cf1、 cf2,、cf3,、cf4 和 cf5。每个对象中都包含名为 q1 和 q2 的属性。其中,虚线表示隐式原型关系。比如,cf3 的原型是 CFp 。构造函数 CF 有两个名为 p1 和 p2 的属性,其中 CFp 、cf1、cf2、cf3、 cf4 或 cf5 都是是不可见的。在 CFp 中的 CFP1 是 cf1、cf2、cf3、 cf4 和 cf5 所共享(不是通过 CF 共享,而是通过 CF 的 prototype 属性共享)。在 CFp 的隐式原型链中发现的任意属性都没有名为 q1、q2 或 CFP1 的属性。需要注意的是 CF 与 CFp 之间不是隐式原型关系。
与大多数基于类的对象语言不同,属性可以通赋值的方式动态地添加到对象中。也就是说,构造函数不需要为所有或任何构造的对象的属性命名或赋值。在上面的图中,可通过在 CFp 的属性中赋新的值来为 cf1、cf2、cf3、 cf4 和 cf5 添加一个新的可被共享的属性。
虽然 ECMAScript 的对象不是纯基于类的语言,它也能可以很方便的基于构造函数、原型对象和方法的常见模式定义像类一样的(class-like)抽象。ECMAScript 的内置对象就是遵循像类一样模式。从 ECMAScript 2015 开始,ECMAScript 语言包含了类的语法定义,允许程序员可以很方便的定义对象,就像内置对象那样定义类样的抽象模式。