你在问题中展示的两个代码示例都使用了原型继承。实际上,你在JavaScript中编写的任何面向对象的代码都是原型继承范式。JavaScript只是没有经典继承。这应该会让事情变得更清晰:
Inheritance
|
+-----------------------------+
| |
v v
Prototypal Classical
|
+------------------------------+
| |
v v
Prototypal Pattern Constructor Pattern
如您所见,原型继承和经典继承是两种不同的继承范式。一些语言,如Self、Lua和JavaScript支持原型继承。然而大多数语言,如C++、Java和C#支持经典继承。
面向对象编程简介
原型继承和经典继承都是面向对象编程范例(即它们处理对象)。对象只是封装实体属性的抽象(即它们在程序中表示真实世界事物)。这就是所谓的“抽象”。
抽象:在计算机程序中表示真实世界事物。
理论上,抽象被定义为“从具体示例中提取公共特征形成的一般概念”。但出于解释目的,我们将使用前述定义。
现在,一些对象具有许多共同点。例如,泥地自行车和哈雷戴维森有许多共同之处。
一个泥地自行车:
![A mud bike.](https://istack.dev59.com/Sk20o.webp)
哈雷戴维森:
![A Harley Davidson](https://istack.dev59.com/4hj6K.webp)
泥地自行车和哈雷戴维森都是自行车。因此,自行车是泥地自行车和哈雷戴维森的概括。
Bike
|
+---------------------------------+
| |
v v
Mud Bike Harley Davidson
在上面的例子中,自行车、越野摩托车和哈雷戴维森都是抽象的。然而,自行车是越野摩托车和哈雷戴维森的更一般的抽象(即,越野摩托车和哈雷戴维森都是自行车的特定类型)。
泛化:更具体的抽象。
在面向对象编程中,我们创建对象(它们是真实世界实体的抽象),并使用类或原型来创建这些对象的泛化。泛化是通过继承创建的。自行车是越野摩托车的泛化。因此,越野摩托车从自行车继承。
经典的面向对象编程
在经典的面向对象编程中,我们有两种类型的抽象:类和对象。如前所述,对象是真实世界实体的抽象。而类则是一个对象或另一个类的抽象(即,它是一种泛化)。例如,请考虑:
+
| Level of Abstraction | Name of Entity | Comments |
+
| 0 | John Doe | Real World Entity. |
| 1 | johnDoe | Variable holding object. |
| 2 | Man | Class of object johnDoe. |
| 3 | Human | Superclass of class Man. |
+
如您所见,在传统面向对象编程语言中,对象只是抽象概念(即所有对象的抽象级别都为1),而类只是一般化概念(即所有类的抽象级别大于1)。
在传统面向对象编程语言中,只能通过实例化类来创建对象:
class Human {
}
class Man extends Human {
}
Man johnDoe = new Man();
在传统的面向对象编程语言中,对象是真实世界实体的抽象,而类是一般化的(即对象或其他类的抽象)。
因此,随着抽象层次的增加,实体变得更为一般化,抽象层次的降低会使实体更具体。从这个意义上说,抽象级别类似于一种从更具体实体到更一般实体的标度。
原型面向对象编程
原型面向对象编程语言比传统的面向对象编程语言简单得多,因为在原型面向对象编程中我们只有一种类型的抽象(即对象)。例如,请考虑:
+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity | Comments |
+----------------------+----------------+---------------------------------------+
| 0 | John Doe | Real World Entity. |
| 1 | johnDoe | Variable holding object. |
| 2 | man | Prototype of object johnDoe. |
| 3 | human | Prototype of object man. |
+----------------------+----------------+---------------------------------------+
正如您在原型对象导向编程语言中所看到的,对象是真实世界实体或其他对象(在这种情况下它们被称为那些抽象对象的原型)的抽象描述。因此,原型是一种概括。
原型对象导向编程语言中的对象可以通过两种方式创建:从零开始创建(即从头开始创建),或者从另一个对象创建(该对象成为新创建的对象的原型):
var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);
在我看来,基于原型的面向对象编程语言比基于经典的面向对象编程语言更强大,因为:
- 只有一种类型的抽象。
- 概括仅是对象。
你现在应该意识到了经典继承和原型继承之间的区别。经典继承仅限于类从其他类继承。然而,原型继承不仅包括原型从其他原型继承,还包括对象从原型继承。
原型-类同构
你一定注意到原型和类非常相似。实际上它们很相似,以至于你可以使用原型来建模类:
function CLASS(base, body) {
if (arguments.length < 2) body = base, base = Object.prototype;
var prototype = Object.create(base, {new: {value: create}});
return body.call(prototype, base), prototype;
function create() {
var self = Object.create(prototype);
return prototype.hasOwnProperty("constructor") &&
prototype.constructor.apply(self, arguments), self;
}
}
通过以上的 CLASS
函数,您可以创建类似类的原型:
var Human = CLASS(function () {
var milliseconds = 1
, seconds = 1000 * milliseconds
, minutes = 60 * seconds
, hours = 60 * minutes
, days = 24 * hours
, years = 365.2425 * days;
this.constructor = function (name, sex, dob) {
this.name = name;
this.sex = sex;
this.dob = dob;
};
this.age = function () {
return Math.floor((new Date - this.dob) / years);
};
});
var Man = CLASS(Human, function (Human) {
this.constructor = function (name, dob) {
Human.constructor.call(this, name, "male", dob);
if (this.age() < 18) throw new Error(name + " is a boy, not a man!");
};
});
var johnDoe = Man.new("John Doe", new Date(1970, 0, 1));
然而相反的情况并非如此(即您不能使用类来建模原型)。这是因为原型是对象,但类不是对象。它们是完全不同类型的抽象。
结论
总之,我们了解到抽象是“从具体示例中提取共同特征形成的一般概念”,泛化是“更具体的抽象”。我们还了解了原型继承和经典继承之间的差异,以及它们如何是同一个硬币的两个面。
最后我想说的是,原型继承有两种模式:原型模式和构造函数模式。原型模式是原型继承的范本模式,而构造函数模式用于使原型继承看起来更像经典继承。个人更喜欢原型模式。