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


119

我已经在Google上搜索了很多链接,而无法很好地了解经典继承与原型继承之间的区别?

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

古典继承

// 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/

通过上面的链接,我了解到我们无法在运行时添加经典继承中的新方法。这样对吗?但是您可以检查上面的代码,我可以在运行时通过prototype添加“ 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);

这类似于古典继承吗?我对什么是原型继承一​​无所知?什么是古典继承?为什么经典继承不好?

您能给我一个简单的例子,以便以一种简单的方式更好地理解它们。

谢谢,

西瓦



5
不知道这里要做什么-代码的第一块原型继承,而不是经典继承。您的第二个代码块根本没有继承!
Alnitak


@alnitak developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... 此链接告诉一个很经典的继承。那就是为什么感到困惑。
SivaRajini

有关为什么您可能希望避免经典继承的更多信息,请参阅我的演讲“经典继承已过时:如何在原型OO中思考” vimeo.com/69255635
Eric Elliott

Answers:


249

您在问题中演示的两个代码示例都使用了原型继承。实际上,您用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);

以我的拙见,原型面向对象的编程语言比经典的面向对象的编程语言更强大,因为:

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

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


结论

总而言之,我们了解到抽象是“通过从特定示例中提取共同特征而形成的一般概念”,而概括是“更具体的抽象的抽象”。我们还了解了原型继承与古典继承之间的区别,以及它们如何都是同一枚硬币的两个面。

在离别笔记上,我想说明一下,原型继承有两种模式:原型模式和构造函数模式。原型模式是原型继承的规范模式,而构造函数模式用于使原型继承看起来更像经典继承。我个人更喜欢原型模式。

PS我是写博客文章“ 为什么原型继承很重要 ”的人,并回答了“ 原型继承相对于经典的好处? ”的问题。我的答案是被接受的答案。


2
感谢您的精彩回答。我需要了解原型模式比构造函数模式更好。任何示例?
SivaRajini 2013年

1
我在博客中写了一个关于构造函数和原型的比较评论:aaditmshah.github.io/why-prototypal-inheritance-matters/…–
Aadit M Shah

那么,当我们使用javascript中的函数来实现继承时,我们会使用某种程度上的经典继承模型,而当我们使用普通对象时则使用原型继承(都在内部遵循原型继承)是正确的吗?
斯瓦尼迪2015年

1
@Swanidhi不。如果您使用的是JavaScript,则您使用的是原型继承模型。但是,JavaScript具有两种原型继承:使用函数(即构造函数模式)和使用对象(即原型模式)。
Aadit M Shah,2015年

5
@Swanidhi不。它仍然是原型。JavaScript没有“类”,因此经典JavaScript(包括构造函数)绝对没有。它仍然是原型继承。人们只是将经典继承与一种奇怪的原型继承形式混为一谈。简单地说,programming with classes = classical inheritanceprogramming with prototypes = prototypal inheritanceprogramming with constructors = weird form of prototypal inheritance that looks a lot like classical inheritance。希望能澄清一些事情。
Aadit M Shah,2015年

8

在进入继承之前,我们将看一下在javascript中创建实例(对象)的两个主要模型:

经典模型:对象是根据蓝图创建的(类)

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

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

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

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

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

无论哪种情况,继承*都是通过使用原型对象链接对象来实现的。

(*基类方法可通过原型对象通过派生类访问,并且不需要在派生类中显式存在。)

这是一个很好的解释,可以更好地理解(http://www.objectplayground.com/


0

狗是动物。苏珊娜是狗。在经典继承中,Animal是的类,Dog的子类Animalsuzanna的实例Dog

在原型继承中,没有类。您有animal一个对象。A 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属性。

道格拉斯·克罗克福德(Douglas Crockford)在他的《 JavaScript:好的零件》一书中对此进行了详尽的探讨。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.