Javascript 中的经典继承与原型继承

我谷歌了这么多链接,却不能很好地理解经典继承和原型继承之间的区别?

我已经从中学到了一些东西,但我仍然对这些概念感到困惑。

古典遗传

// Shape - superclass
function Shape() {
this.x = 0;
this.y = 0;
}


//superclass method
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info("Shape moved.");
};


// Rectangle - subclass
function Rectangle() {
Shape.call(this); //call super constructor.
}


//subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);

经典继承在内部使用原型继承吗?

Http://aaditmshah.github.io/why-prototypal-inheritance-matters/

从上面的链接,我学习了 我们不能在运行时在经典继承中添加新方法。是这样吗?但是你可以检查上面的代码 我可以通过原型在运行时添加“ move”方法和任何方法。这就是基于原型的经典继承?如果是这样,那么实际的经典继承和原型继承又是什么呢?我对此很困惑。

原型继承。

function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference: function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);

这是否类似于古典遗传?我完全搞不懂什么是原型继承?什么是古典遗传?为什么经典继承不好?

你能给我一个简单的例子,更好地理解这些在一个简单的方式。

谢谢,

湿婆

72043 次浏览

您在问题中演示的两个代码示例都使用了原型继承。事实上,您用 JavaScript 编写的任何面向对象代码都是一种原型继承的范例。JavaScript 根本就没有经典继承。这应该会让事情变得清晰一些:

                                   Inheritance
|
+-----------------------------+
|                             |
v                             v
Prototypal                     Classical
|
+------------------------------+
|                              |
v                              v
Prototypal Pattern             Constructor Pattern

正如您所看到的,原型继承和经典继承是两种不同的继承范式。像 Self、 Lua 和 JavaScript 这样的语言支持原型继承。然而,像 C + + 、 Java 和 C # 这样的大多数语言都支持经典继承。


面向对象程序设计简介

原型继承和经典继承都是面向对象程序设计范例(即它们处理对象)。对象是简单的抽象,它们封装了真实世界实体的属性(也就是说,它们代表程序中的真实词汇)。这就是所谓的抽象。

摘要: 在计算机程序中对真实世界事物的表现。

从理论上讲,抽象被定义为“从具体实例中提取共同特征而形成的一般概念”。然而,为了这个解释,我们将使用前面提到的定义。

现在一些对象有很多共同点。例如,泥地自行车和哈雷戴维森有很多共同点。

泥浆自行车:

A mud bike.

一辆哈雷戴维森:

A Harley Davidson

泥地自行车和哈雷戴维森都是自行车。因此,自行车是泥地自行车和哈雷戴维森的概括。

                   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.              |
+----------------------+----------------+---------------------------------------+

正如你在原型面向对象程序设计语言中看到的那样,对象是现实世界实体(简称对象)或其他对象(简称抽象对象的原型)的抽象。因此,原型是一种概括。

原型面向对象程序设计语言中的对象可以创建为 ex-nihilo (无中生有)或者从另一个对象(成为新创建对象的原型) :

var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);

在我看来,原型面向对象程序设计语言比古典面向对象程序设计语言更强大,因为:

  1. 抽象只有一种类型。
  2. 泛化就是简单的对象。

到目前为止,您一定已经认识到了经典继承和原型继承之间的区别。经典继承仅限于继承其他类的类。然而,原型继承不仅包括从其他原型继承的原型,还包括从原型继承的对象。


原型-类同构

您肯定已经注意到,原型和类非常相似。那倒是真的。是的。事实上,它们是如此相似,以至于您可以使用原型来为类建模:

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));

然而,相反的情况并非如此(例如,您不能使用类来为原型建模)。这是因为原型是对象,而类不是对象。它们是一种完全不同的抽象类型。


结论

总之,我们知道抽象是 从具体例子中提取共同特征形成的一般概念,泛化是 “一个更具体的抽象的抽象”。我们还了解了原型继承和古典继承之间的区别,以及它们是如何成为同一枚硬币的两面的。

在最后,我想指出,原型继承有两种模式: 原型模式和构造函数模式。原型模式是原型继承的规范模式,而构造函数模式用于使原型继承看起来更像经典继承。我个人比较喜欢原型模式。

在开始继承之前,我们先来看一下在 javascript 中创建实例(对象)的两个 初选模型:

经典模型: 对象是从一个蓝图(类)中创建的

class Person {
fn() {...}
} // or constructor function say, function Person() {}


// create instance
let person = new Person();

原型模型: Object 是直接从另一个对象创建的。

// base object
let Person = { fn(){...} }


// instance
let person = Object.create(Person);

在这两种情况下,继承 * 都是通过使用原型对象链接对象来实现的。

(* 基类方法可通过。派生类,而不需要在派生类中显式存在。)

这里有一个很好的解释更好地理解(http://www.objectplayground.com/)

狗就是动物。苏珊娜是条狗。在经典继承中,Animal是一个类,DogAnimal的一个子类,suzannaDog的一个实例。

在原型继承中,没有类。你有一个 animal,这是一个对象。dog是另一个对象,它克隆并扩展 animal(原型对象)。suzanna是第三个对象,它复制并扩展 dog

let animal = {hasChlorophyl: false};


let dog = Object.create(animal);
Object.assign(dog, {
speak() {
console.log("Woof!");
}
});


let suzanna = Object.create(dog);
Object.assign(suzanna, {
name: "Suzanna"
});


suzanna.speak();

如果你编写 Dog而不是 dog,特别是如果你使 Dog成为某种“构造函数”函数,那么 您不是在进行原型继承,而是在进行(伪)经典继承。您使用 Object.create()来实现这一点并不意味着您正在进行原型继承。

事实上,JavaScript 只支持原型继承。令人困惑的 new操作符和 .prototype属性的存在是为了使原型继承看起来像(伪)经典继承。

道格拉斯·克罗克福特在他的《 JavaScript: 好的部分》一书中详细探讨了这个问题。