您在问题中演示的两个代码示例都使用了原型继承。实际上,您用JavaScript编写的任何面向对象的代码都是原型继承的范例。JavaScript根本就没有经典继承。这应该清除一些东西:
Inheritance
|
+-----------------------------+
| |
v v
Prototypal Classical
|
+------------------------------+
| |
v v
Prototypal Pattern Constructor Pattern
如您所见,原型继承和经典继承是继承的两种不同范例。诸如Self,Lua和JavaScript之类的某些语言支持原型继承。但是,大多数语言(例如C ++,Java和C#)都支持经典继承。
面向对象编程的快速概述
原型继承和经典继承都是面向对象的编程范例(即,它们处理对象)。对象仅仅是封装现实世界实体属性的抽象(即,它们表示程序中的真实单词事物)。这称为抽象。
抽象:计算机程序中现实世界事物的表示。
从理论上讲,抽象定义为“通过从特定示例中提取共同特征而形成的一般概念”。但是,为了便于说明,我们将改用上述定义。
现在,某些对象有很多共同点。例如,越野车和哈雷戴维森(Harley Davidson)有很多共同点。
泥地自行车:
哈雷戴维森:
泥地自行车和哈雷戴维森都是自行车。因此,自行车既是泥泞自行车又是哈雷戴维森的概括。
Bike
|
+---------------------------------+
| |
v v
Mud Bike Harley Davidson
在上面的示例中,自行车,泥地自行车和Harley Davidson都是抽象的。但是,自行车是泥地自行车和Harley Davidson的更一般的抽象(即泥地自行车和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));
但是,相反情况并非如此(即,您不能使用类来对原型进行建模)。这是因为原型是对象,而类不是对象。它们是完全不同的抽象类型。
结论
总而言之,我们了解到抽象是“通过从特定示例中提取共同特征而形成的一般概念”,而概括是“更具体的抽象的抽象”。我们还了解了原型继承与古典继承之间的区别,以及它们如何都是同一枚硬币的两个面。
在离别笔记上,我想说明一下,原型继承有两种模式:原型模式和构造函数模式。原型模式是原型继承的规范模式,而构造函数模式用于使原型继承看起来更像经典继承。我个人更喜欢原型模式。
PS我是写博客文章“ 为什么原型继承很重要 ”的人,并回答了“ 原型继承相对于经典的好处? ”的问题。我的答案是被接受的答案。