gpt4 book ai didi

javascript - 原型(prototype)继承相对于经典继承的好处?

转载 作者:行者123 更新时间:2023-12-01 17:26:10 25 4
gpt4 key购买 nike

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

最佳答案

我知道这个答案晚了 3 年,但我真的认为当前的答案没有提供关于 how prototypal inheritance is better than classical inheritance 的足够信息。 .

首先让我们看看 JavaScript 程序员为保护原型(prototype)继承而陈述的最常见的论点(我从当前的答案池中获取这些论点):

  • 这很简单。
  • 它很强大。
  • 它导致更小、更少冗余的代码。
  • 它是动态的,因此更适合动态语言。

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

    我认为原型(prototype)继承的问题在于它是从 JavaScript 的 Angular 来解释的。我喜欢 JavaScript,但 JavaScript 中的原型(prototype)继承是错误的。与经典继承不同,原型(prototype)继承有两种模式:
  • 原型(prototype)继承的原型(prototype)模式。
  • 原型(prototype)继承的构造函数模式。

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

    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 中使用构造函数时,他们认为构造函数是从其他构造函数继承的。这是错误的。在原型(prototype)继承中,对象从其他对象继承。构造函数永远不会出现。这是大多数人困惑的地方。

    来自 Java 等具有经典继承的语言的人更加困惑,因为尽管构造函数看起来像类,但它们的行为却不像类。如 Douglas Crockford说:

    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.



    你有它。直接从马嘴里说出来。

    真正的原型(prototype)继承

    原型(prototype)继承是关于对象的。对象从其他对象继承属性。这里的所有都是它的。有两种使用原型(prototype)继承创建对象的方法:
  • 创建一个全新的对象。
  • 克隆现有对象并扩展它。

  • 注: JavaScript 提供了两种克隆对象的方法 - delegationconcatenation .从今以后,我将使用“克隆”一词专指通过委托(delegate)进行的继承,而将“复制”一词专指通过串联进行的继承。

    够了。让我们看一些例子。假设我有一个半径为 5 的圆:
    var circle = {
    radius: 5
    };

    我们可以从半径计算圆的面积和周长:
    circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
    };

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

    现在我想创建另一个半径为 10 的圆.一种方法是:
    var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
    };

    然而 JavaScript 提供了更好的方法 - delegation . Object.create 函数用于执行此操作:
    var circle2 = Object.create(circle);
    circle2.radius = 10;

    就这样。您刚刚在 JavaScript 中进行了原型(prototype)继承。是不是很简单?你拿一个物体,克隆它,改变你需要的任何东西,嘿,很快——你得到了一个全新的物体。

    现在您可能会问,“这怎么简单?每次我想创建一个新圆时,我都需要克隆 circle 并手动为其分配一个半径”。那么解决方案是使用一个函数来为你做繁重的工作:
    function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
    }

    var circle2 = createCircle(10);

    事实上,您可以将所有这些组合成一个单一的对象字面量,如下所示:
    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 中的原型(prototype)继承

    如果您注意到上述程序中的 create函数创建 circle 的克隆, 分配一个新的 radius到它然后返回它。这正是 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 中的构造器模式是原型(prototype)模式的倒置。您创建一个构造函数,而不是创建一个对象。 new关键字绑定(bind) this构造函数内指向 prototype 的克隆的指针的构造函数。

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

    他们没有考虑从其他对象继承的对象,而是考虑从其他构造函数继承的构造函数,然后变得完全困惑。

    应该避免使用 JavaScript 中的构造函数模式还有很多其他原因。你可以在我的博客文章中阅读它们: Constructors vs Prototypes

    那么原型(prototype)继承相对于经典继承有什么好处呢?让我们再次回顾一下最常见的论点,并解释原因。

    1.原型(prototype)继承很简单

    CMS在他的回答中说:

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



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

    因此,我们只需要两件事就可以使原型(prototype)继承工作:
  • 一种创建新对象的方法(例如对象字面量)。
  • 一种扩展现有对象的方法(例如 Object.create )。

  • 相比之下,经典继承要复杂得多。在经典继承中,您有:
  • 类(class)。
  • 目的。
  • 接口(interface)。
  • 抽象类。
  • 期末类。
  • 虚拟基类。
  • 构造函数。
  • 破坏者。

  • 你明白了。关键是原型(prototype)继承更容易理解,更容易实现,也更容易推理。

    正如 Steve Yegge 在他的经典博客文章“ 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)发现类(class)更适合使用。这给了他们一种虚假的安全感。

    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.



    正如我之前所说,类(class)给人们一种错误的安全感。例如你得到太多 NullPointerException s 在 Java 中,即使您的代码非常清晰。我发现经典继承通常会妨碍编程,但也许这只是 Java。 Python 有一个惊人的经典继承系统。

    2. 原型(prototype)继承是强大的

    大多数具有经典背景的程序员认为经典继承比原型(prototype)继承更强大,因为它具有:
  • 私有(private)变量。
  • 多重继承。

  • 这种说法是错误的。我们已经知道 JavaScript 支持 private variables via closures ,但是多重继承呢? JavaScript 中的对象只有一个原型(prototype)。

    事实是原型(prototype)继承支持从多个原型(prototype)继承。原型(prototype)继承仅仅意味着一个对象从另一个对象继承。居然还有 two ways to implement prototypal inheritance :
  • 委托(delegate)或差分继承
  • 克隆或串联继承

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

    当然很多程序员并不认为这是真正的继承,因为 instanceof isPrototypeOf 否则说。但是,这可以通过在通过串联从原型(prototype)继承的每个对象上存储原型(prototype)数组来轻松解决:
    function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
    prototypes.some(prototypeOf, prototype);
    }

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

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

    然而,在 JavaScript 中,您很可能永远不会听说过菱形问题,因为您可以准确地控制您希望继承哪些属性以及从哪些原型(prototype)中继承。

    3. 原型(prototype)继承的冗余更少

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

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

    Java 因这种行为而臭名昭著。我清楚地记得 Bob Nystrom在他的博客文章中提到了以下关于 Pratt Parsers 的轶事:

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



    再说一次,我认为那只是因为 Java 太烂了。

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

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

    4.原型(prototype)继承是动态的

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

    这在经典继承中是不可能的,因为一旦创建了一个类,你就不能在运行时修改它。这可能是原型(prototype)继承相对于经典继承的最大优势,它应该是最重要的。不过我喜欢把最好的留到最后。

    结论

    原型(prototype)继承很重要。重要的是要教育 JavaScript 程序员为什么要放弃原型(prototype)继承的构造函数模式而支持原型(prototype)继承的原型(prototype)模式。

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

    使用原型(prototype)模式不仅可以更容易地解释原型(prototype)继承,而且还可以成为更好的程序员。

    如果您喜欢这个答案,那么您还应该阅读我关于“ Why Prototypal Inheritance Matters”的博客文章。相信我,你不会失望的。

    关于javascript - 原型(prototype)继承相对于经典继承的好处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2800964/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com