原型继承优于经典的好处?

问题描述:

所以这些年来我终于停止了我的脚,并决定正确学习JavaScript。语言设计中最令人头疼的元素之一是它的继承实现。有Ruby经验,我很高兴看到闭包和动态打字;但是对于我的生活,无法弄清楚使用其他实例进行继承的对象实例会带来什么好处。

So I finally stopped dragging my feet all these years and decided to learn JavaScript "properly". One of the most head-scratching elements of the languages design is it's implementation of inheritance. Having experience in Ruby, I was really happy to see closures and dynamic typing; but for the life of me can't figure out what benefits are to be had from object instances using other instances for inheritance.

我知道这个答案迟了3年但我真的认为目前的答案没有提供足够的信息原型继承如何比经典继承更好

I know that this answer is 3 years late but I really think the current answers do not provide enough information about how prototypal inheritance is better than classical inheritance.

首先让我们看看最常见的参数JavaScript程序员声称保护原型继承(我从当前的答案池中获取这些参数):

First let's see the most common arguments JavaScript programmers state in defence of prototypal inheritance (I'm taking these arguments from the current pool of answers):


  1. 这很简单。

  2. 它功能强大。

  3. 它会导致更小,更少冗余的代码。

  4. 它是动态的,因此它更适合动态语言。

  1. It's simple.
  2. It's powerful.
  3. It leads to smaller, less redundant code.
  4. It's dynamic and hence it's better for dynamic languages.

现在这些参数都是有效的,但没人知道困扰解释原因。这就像告诉孩子学习数学很重要。当然可以,但孩子当然不在乎;并且你不能让像孩子这样的孩子说这很重要。

Now these arguments are all valid, but nobody has bothered explaining why. It's like telling a child that studying Maths is important. Sure it is, but the child certainly doesn't care; and you can't make a child like Maths by saying that it's important.

我认为原型继承的问题在于它是从JavaScript的角度来解释的。我喜欢JavaScript,但JavaScript中的原型继承是错误的。与经典继承不同,有两种原型继承模式:

I think the problem with prototypal inheritance is that it's explained from the perspective of JavaScript. I love JavaScript, but prototypal inheritance in JavaScript is wrong. Unlike classical inheritance there are two patterns of prototypal inheritance:


  1. 原型继承的原型模式。

  2. 原型继承的构造函数模式。

不幸的是,JavaScript使用原型继承的构造函数模式。这是因为在创建JavaScript时, Brendan Eich (JS的创建者)希望它看起来像Java(具有经典继承):

Unfortunately JavaScript uses the constructor pattern of prototypal inheritance. This is because when JavaScript was created, Brendan Eich (the creator of JS) wanted it to look like Java (which has classical inheritance):


我们将它作为Java的小兄弟推动,作为Visual Basic等补充语言当时是微软语言家族的C ++。

And we were pushing it as a little brother to Java, as a complementary language like Visual Basic was to C++ in Microsoft’s language families at the time.

这很糟糕,因为当人们在JavaScript中使用构造函数时,他们会想到构造函数继承自其他建设者。这是错的。在原型继承中,对象继承自其他对象。构造函数永远不会出现。这就是让大多数人感到困惑的原因。

This is bad because when people use constructors in JavaScript they think of constructors inheriting from other constructors. This is wrong. In prototypal inheritance objects inherit from other objects. Constructors never come into the picture. This is what confuses most people.

来自像Java这样具有经典继承的语言的人变得更加困惑,因为虽然构造函数看起来像类,但它们的行为并不像类。正如 Douglas Crockford 所述:

People from languages like Java, which has classical inheritance, get even more confused because although constructors look like classes they don't behave like classes. As Douglas Crockford stated:


这种间接性旨在使语言对经过专业训练的程序员更熟悉,但未能做到这一点,正如我们从Java程序员对JavaScript的非常低级的看法中看到的那样。 JavaScript的构造函数模式并没有吸引经典人群。它也掩盖了JavaScript真正的原型性质。因此,很少有程序员知道如何有效地使用该语言。

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

你有它。直接从马的口中。

There you have it. Straight from the horse's mouth.

原型继承完全是关于对象的。对象从其他对象继承属性。这里的所有都是它的。有两种使用原型继承创建对象的方法:

Prototypal inheritance is all about objects. Objects inherit properties from other objects. That's all there is to it. There are two ways of creating objects using prototypal inheritance:


  1. 创建一个全新的对象。

  2. 克隆现有对象并对其进行扩展。

注意: JavaScript提供了两种克隆对象的方法 - 委托和连接。此后,我将使用clone一词专门通过委托引用继承,单词copy通过连接专门引用继承。

Note: JavaScript offers two ways to clone an object - delegation and concatenation. Henceforth I'll use the word "clone" to exclusively refer to inheritance via delegation, and the word "copy" to exclusively refer to inheritance via concatenation.

足够的谈话。我们来看一些例子。假设我的半径为 5

Enough talk. Let's see some examples. Say I have a circle of radius 5:

var circle = {
    radius: 5
};

我们可以从半径计算圆的面积和周长:

We can calculate the area and the circumference of the circle from its radius:

circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

circle.circumference = function () {
    return 2 * Math.PI * this.radius;
};

现在我要创建另一个半径 10 $ c $的圆圈C>。一种方法是:

Now I want to create another circle of radius 10. One way to do this would be:

var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
};

然而JavaScript提供了更好的方法 - 委托。 Crockford的 Object.create 函数用于执行此操作:

However JavaScript provides a better way - delegation. Crockford's Object.create function is used to do this:

var circle2 = Object.create(circle);
circle2.radius = 10;

这就是全部。您刚刚在JavaScript中进行了原型继承。那不简单吗?你拿一个物体,克隆它,改变你需要的东西,然后嘿嘿 - 你得到了一个全新的物体。

That's all. You just did prototypal inheritance in JavaScript. Wasn't that simple? You take an object, clone it, change whatever you need to, and hey presto - you got yourself a brand new object.

现在你可能会问,这是怎么回事简单?每次我想要创建一个新的圆圈时,我需要克隆 circle 并手动为其指定半径。那么解决方案就是使用一个函数为你做繁重的工作:

Now you might ask, "How is this simple? Every time I want to create a new circle I need to clone circle and manually assign it a radius". Well the solution is to use a function to do the heavy lifting for you:

function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
}

var circle2 = createCircle(10);

事实上,您可以将所有这些组合成一个对象文字,如下所示:

In fact you can combine all of this into a single object literal as follows:

var circle = {
    radius: 5,
    create: function (radius) {
        var circle = Object.create(this);
        circle.radius = radius;
        return circle;
    },
    area: function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    },
    circumference: function () {
        return 2 * Math.PI * this.radius;
    }
};

var circle2 = circle.create(10);



JavaScript中的原型继承



如果你在上面的程序中注意 create 函数创建一个 circle 的克隆,分配一个新的 radius 然后返回它。这正是构造函数在JavaScript中的作用:

Prototypal Inheritance in JavaScript

If you notice in the above program the create function creates a clone of circle, assigns a new radius to it and then returns it. This is exactly what a constructor does in JavaScript:

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

JavaScript中的构造函数模式是反转的原型模式。您可以创建构造函数,而不是创建对象。 new 关键字将构造函数中的 this 指针绑定到 prototype的克隆的构造函数。

The constructor pattern in JavaScript is the prototypal pattern inverted. Instead of creating an object you create a constructor. The new keyword binds the this pointer inside the constructor to a clone of the prototype of the constructor.

听起来有点混乱?这是因为JavaScript中的构造函数模式不必要地使事情复杂化。这是大多数程序员难以理解的。

Sounds confusing? It's because the constructor pattern in JavaScript unnecessarily complicates things. This is what most programmers find difficult to understand.

除了考虑从其他对象继承的对象之外,他们认为构造函数继承自其他构造函数,然后变得完全混淆。

Instead of thinking of objects inheriting from other objects they think of constructors inheriting from other constructors and then become utterly confused.

还有很多其他原因可以避免JavaScript中的构造函数模式。你可以在我的博客文章中阅读它们:构造函数与原型

There's a whole bunch of other reasons why the constructor pattern in JavaScript should be avoided. You can read about them in my blog post here: Constructors vs Prototypes

那么原型继承优于经典继承有什么好处?让我们再次讨论最常见的论点,并解释为什么

So what are the benefits of prototypal inheritance over classical inheritance? Let's go through the most common arguments again, and explain why.

CMS 在他的回答中陈述:


在我看来,原型继承的主要好处是简单。

In my opinion the major benefit of prototypal inheritance is its simplicity.

让我们考虑一下我们刚刚做了什么。我们创建了一个对象 circle ,其半径为 5 。然后我们克隆它并给克隆半径 10

Let's consider what we just did. We created an object circle which had a radius of 5. Then we cloned it and gave the clone a radius of 10.

因此我们只需要两件事来制作原型继承工作:

Hence we only need two things to make prototypal inheritance work:


  1. 一种创建新对象的方法(例如对象文字)。

  2. A扩展现有对象的方法(例如 Object.create )。

  1. A way to create a new object (e.g. object literals).
  2. A way to extend an existing object (e.g. Object.create).

In对比经典继承要复杂得多。在经典继承中,你有:

In contrast classical inheritance is much more complicated. In classical inheritance you have:


  1. Classes。

  2. Object。

  3. 接口。

  4. 抽象类。

  5. 最终类。

  6. 虚拟基类。

  7. 构造函数。

  8. Destructors。

  1. Classes.
  2. Object.
  3. Interfaces.
  4. Abstract Classes.
  5. Final Classes.
  6. Virtual Base Classes.
  7. Constructors.
  8. Destructors.

你得到这个想法。关键是原型继承更容易理解,更容易实现,更容易推理。

You get the idea. The point is that prototypal inheritance is easier to understand, easier to implement, and easier to reason about.

正如Steve Yegge在其经典博客文章中所说的那样 N00b的肖像

As Steve Yegge puts it in his classical blog post "Portrait of a N00b":


元数据是其他任何形式的描述或模型。代码中的注释只是计算的自然语言描述。元数据元数据的作用是它不是绝对必要的。如果我有一只带有一些谱系文书的狗,我丢失了文书工作,我仍然有一只完全有效的狗。

Metadata is any kind of description or model of something else. The comments in your code are just a a natural-language description of the computation. What makes metadata meta-data is that it's not strictly necessary. If I have a dog with some pedigree paperwork, and I lose the paperwork, I still have a perfectly valid dog.

同样感知类只是元数据。继承不严格要求类。然而,有些人(通常是n00bs)找到更舒适的课程。它给了他们一种虚假的安全感。

In the same sense classes are just meta-data. Classes aren't strictly required for inheritance. However some people (usually n00bs) find classes more comfortable to work with. It gives them a false sense of security.


嗯,我们也知道静态类型只是元数据。它们是针对两种读者的专门评论:程序员和编译器。静态类型讲述了有关计算的故事,可能是为了帮助两个读者组理解程序的意图。但是静态类型可以在运行时抛弃,因为最后它们只是风格化的注释。他们就像是谱系文书工作:它可能使某种不安全的性格类型对他们的狗更开心,但狗肯定不在乎。

Well, we also know that static types are just metadata. They're a specialized kind of comment targeted at two kinds of readers: programmers and compilers. Static types tell a story about the computation, presumably to help both reader groups understand the intent of the program. But the static types can be thrown away at runtime, because in the end they're just stylized comments. They're like pedigree paperwork: it might make a certain insecure personality type happier about their dog, but the dog certainly doesn't care.



NullPointerException 。我发现经典继承通常会妨碍编程,但也许只是Java。 Python有一个惊人的经典继承系统。

As I stated earlier, classes give people a false sense of security. For example you get too many NullPointerExceptions in Java even when your code is perfectly legible. I find classical inheritance usually gets in the way of programming, but maybe that's just Java. Python has an amazing classical inheritance system.

大多数来自古典背景的程序员认为经典继承比原型继承更强大,因为它具有:

Most programmers who come from a classical background argue that classical inheritance is more powerful than prototypal inheritance because it has:


  1. 私有变量。

  2. 多重继承。

这种说法是错误的。我们已经知道JavaScript支持私有变量通过闭包,但是关于多重继承? JavaScript中的对象只有一个原型。

This claim is false. We already know that JavaScript supports private variables via closures, but what about multiple inheritance? Objects in JavaScript only have one prototype.

事实上,原型继承支持从多个原型继承。原型继承只是意味着一个对象继承自另一个对象。实际上有两种方法实现原型继承

The truth is that prototypal inheritance supports inheriting from multiple prototypes. Prototypal inheritance simply means one object inheriting from another object. There are actually two ways to implement prototypal inheritance:


  1. 委派或差异继承

  2. 克隆或连接继承

是JavaScript只允许对象委托给另一个对象。但是,它允许您复制任意数量的对象的属性。例如 _.extend 就是这样。

Yes JavaScript only allows objects to delegate to one other object. However it allows you to copy the properties of an arbitrary number of objects. For example _.extend does just this.

当然,许多程序员并不认为这是真正的继承,因为 instanceof isPrototypeOf 另有说法。但是,通过在每个通过串联继承原型的对象上存储原型数组可以很容易地解决这个问题:

Of course many programmers don't consider this to be true inheritance because instanceof and isPrototypeOf say otherwise. However this can be easily remedied by storing an array of prototypes on every object which inherits from a prototype via concatenation:

function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
        prototypes.some(prototypeOf, prototype);
}

因此,原型继承与经典继承一样强大。实际上它比经典继承更强大,因为在原型继承中,您可以手动选择要复制的属性以及从不同原型中省略哪些属性。

Hence prototypal inheritance is just as powerful as classical inheritance. In fact it's much more powerful than classical inheritance because in prototypal inheritance you can hand pick which properties to copy and which properties to omit from different prototypes.

在经典继承中,它是不可能的(或者至少非常困难)选择要继承的属性。他们使用虚拟基类和接口来解决 the diamond问题

In classical inheritance it's impossible (or at least very difficult) to choose which properties you want to inherit. They use virtual base classes and interfaces to solve the diamond problem.

在JavaScript中,您很可能永远不会听到钻石问题,因为您可以精确控制您希望继承的属性以及原型。

In JavaScript however you'll most likely never hear of the diamond problem because you can control exactly which properties you wish to inherit and from which prototypes.

这一点有点难以解释,因为经典继承不一定会导致更多的冗余代码。实际上,继承(无论是经典还是原型)用于减少代码中的冗余。

This point is a little more difficult to explain because classical inheritance doesn't necessarily lead to more redundant code. In fact inheritance, whether classical or prototypal, is used to reduce the redundancy in code.

一个论点可能是大多数具有经典继承的编程语言是静态类型的并且需要user用于显式声明类型(与Haskell不同,它具有隐式静态类型)。因此,这会导致更详细的代码。

One argument could be that most programming languages with classical inheritance are statically typed and require the user to explicitly declare types (unlike Haskell which has implicit static typing). Hence this leads to more verbose code.

Java因此行为而臭名昭着。我清楚地记得 Bob Nystrom 在他的博客文章中提到以下轶事 Pratt Parsers

Java is notorious for this behavior. I distinctly remember Bob Nystrom mentioning the following anecdote in his blog post about Pratt Parsers:


你一定要喜欢Java的请签一式四份这里的官僚主义。

You gotta love Java's "please sign it in quadruplicate" level of bureaucracy here.

我认为这只是因为Java糟透了。

Again, I think that's only because Java sucks so much.

一个有效的论点是,并非所有具有经典继承的语言都支持多重继承。再次想到Java。是Java有接口,但这还不够。有时你确实需要多重继承。

One valid argument is that not all languages which have classical inheritance support multiple inheritance. Again Java comes to mind. Yes Java has interfaces, but that's not sufficient. Sometimes you really need multiple inheritance.

由于原型继承允许多重继承,如果使用原型继承而不是使用具有原型继承的语言编写需要多继承的代码则不那么多余。经典继承但没有多重继承。

Since prototypal inheritance allows for multiple inheritance, code which requires multiple inheritance is less redundant if written using prototypal inheritance rather than in a language which has classical inheritance but no multiple inheritance.

原型继承的一个最重要的优点是,您可以在创建原型后为其添加新属性。这允许您向原型添加新方法,该方法将自动提供给委托给该原型的所有对象。

One of the most important advantages of prototypal inheritance is that you can add new properties to prototypes after they are created. This allows you to add new methods to a prototype which will be automatically made available to all the objects which delegate to that prototype.

这在经典继承中是不可能的,因为一次创建一个类,您无法在运行时修改它。这可能是原型继承优于经典继承的最大优势,它应该是最重要的。但是我喜欢在最后保存最好的结果。

This is not possible in classical inheritance because once a class is created you can't modify it at runtime. This is probably the single biggest advantage of prototypal inheritance over classical inheritance, and it should have been at the top. However I like saving the best for the end.

原型继承很重要。让JavaScript程序员了解为什么放弃原型继承的构造函数模式以支持原型继承的原型模式是很重要的。

Prototypal inheritance matters. It's important to educate JavaScript programmers on why to abandon the constructor pattern of prototypal inheritance in favor of the prototypal pattern of prototypal inheritance.

我们需要开始正确地教授JavaScript意味着向新程序员展示如何使用原型模式而不是构造函数模式编写代码。

We need to start teaching JavaScript correctly and that means showing new programmers how to write code using the prototypal pattern instead of the constructor pattern.

使用原型模式解释原型继承不仅更容易,而且它也会让更好的程序员。

Not only will it be it easier to explain prototypal inheritance using the prototypal pattern, but it will also make better programmers.

如果您喜欢这个答案,那么您还应该阅读我的博客文章为什么原型继承很重要。相信我,你不会失望。

If you liked this answer then you should also read my blog post on "Why Prototypal Inheritance Matters". Trust me, you will not be disappointed.