基于类 vs 基于原型的语言

基于类的面向对象语言,比如 Java 和 C++,是构建在两个不同实体之上的:类和实例。

  • 一个类(class)定义了某一对象集合所具有的特征性属性(可以将 Java 中的方法和域以及 C++ 中的成员都视作属性)。类是抽象的,而不是其所描述的对象集合中的任何特定的个体。例如 Employee 类可以用来表示所有雇员的集合。
  • 另一方面,一个实例(instance)是一个类的实例化。例如, Victoria 可以是 Employee 类的一个实例,表示一个特定的雇员个体。实例具有和其父类完全一致的属性,不多也不少。

基于原型的语言(如 JavaScript)并不存在这种区别:它只有对象

基于原型的语言具有所谓**原型对象(prototypical object)**的概念。

原型对象可以作为一个模板,新对象可以从中获得原始的属性。

任何对象都可以指定其自身的属性,既可以是创建时也可以在运行时创建。

而且,任何对象都可以作为另一个对象的原型(prototype),从而允许后者共享前者的属性

定义类

在基于类的语言中,需要专门的类定义(class definition)来定义类

在定义类时,允许定义被称为构造器(constructor)的特殊的方法来创建该类的实例

在构造器方法中,可以指定实例的属性的初始值并做一些其他的操作。你可以通过使用 new 操作符来创建类的实例。

JavaScript 大体上与之类似,但并没有专门的类定义,你通过定义构造函数的方式来创建一系列有着特定初始值和方法的对象

任何JavaScript函数都可以被用作构造函数。你也可以使用 new 操作符来创建一个新对象。

备注:ES6中引入了类定义,但它实际上是已有的原型继承方式的语法糖而已,并没有引入新的面向对象继承模型

子类和继承

基于类的语言是通过对类的定义中构建类的层级结构的。

在类定义中,可以指定新的类是一个现存的类的子类。子类将继承父类的全部属性,并可以添加新的属性或者修改继承的属性。

例如,假设 Employee 类只有 namedept 属性,而 ManagerEmployee 的子类并添加了 reports 属性。这时,Manager 类的实例将具有所有三个属性:namedeptreports

JavaScript 通过将构造器函数与原型对象相关联的方式来实现继承。这样,您可以创建完全一样的 EmployeeManager 示例,不过需要使用略微不同的术语。

首先,定义Employee构造函数,在该构造函数内定义name、dept属性;接下来,定义Manager构造函数,在该构造函数内调用Employee构造函数,并定义reports属性;最后,将一个获得了Employee.prototype(Employee构造函数原型)的新对象赋予manager构造函数,以作为Manager构造函数的原型。之后当你创建新的Manager对象实例时,该实例会从Employee对象继承name、dept属性。

添加和移除属性

在基于类的语言中,通常在编译时创建类,然后在编译时或者运行时对类的实例进行实例化。一旦定义了类,无法对类的属性进行更改。

然而,在 JavaScript 中,允许运行时添加或者移除任何对象的属性。如果您为一个对象中添加了一个属性,而这个对象又作为其它对象的原型,则以该对象作为原型的所有其它对象也将获得该属性。

差异总结

下面的表格摘要给出了上述区别。本节的后续部分将描述有关使用 JavaScript 构造器和原型创建对象层级结构的详细信息,并将其与在 Java 中的做法加以对比。

基于类的(Java)基于原型的(JavaScript)
类和实例是不同的事物。所有对象均为实例。
通过类定义来定义类;通过构造器方法来实例化类。通过构造器函数来定义和创建一组对象。
通过 new 操作符创建单个对象。相同。
通过类定义来定义现存类的子类,从而构建对象的层级结构。指定一个对象作为原型并且与构造函数一起构建对象的层级结构
遵循类链继承属性。遵循原型链继承属性。
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。构造器函数或原型指定实例的初始属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。