原型继承相对于经典继承的好处? [英] Benefits of prototypal inheritance over classical?

查看:28
本文介绍了原型继承相对于经典继承的好处?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以这些年来我终于停止了拖延,并决定正确地"学习 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. 它是动态的,因此更适合动态语言.

现在这些论点都是有效的,但没有人费心解释原因.这就像告诉孩子学习数学很重要一样.当然是,但孩子肯定不在乎;你不能说数学很重要就让孩子喜欢数学.

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 提供了两种克隆对象的方法 - 委托串联.以后我将使用克隆"一词专指通过委托进行的继承,使用复制"一词专指通过串联继承.

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 的圆.一种方法是:

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 提供了更好的方法 - 委托.Object.create 函数用于执行此操作:

However JavaScript provides a better way - delegation. The 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. 一种扩展现有对象的方法(例如 Object.create).

相比之下,经典继承要复杂得多.在经典继承中,您有:

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

  1. 课程.
  2. 对象.
  3. 接口.
  4. 抽象类.
  5. 期末班.
  6. 虚拟基类.
  7. 构造函数.
  8. 析构函数.

你懂的.关键是原型继承更容易理解,更容易实现,也更容易推理.

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

正如史蒂夫耶格在他的经典博客文章中所说的那样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.

正如我之前所说,课程会给人们一种虚假的安全感.例如,即使您的代码非常清晰,您在 Java 中也会遇到太多 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.

当然很多程序员并不认为这是真正的继承,因为 instanceofisPrototypeOf 否则说.但是,这可以通过在通过串联从原型继承的每个对象上存储原型数组来轻松解决:

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.

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

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.

一个论点可能是大多数具有经典继承的编程语言都是静态类型的,并且需要用户显式声明类型(与具有隐式静态类型的 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 在他的博客文章中提到了以下轶事普拉特解析器:

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.

这篇关于原型继承相对于经典继承的好处?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆