原型继承相对于经典的好处?

2020/10/07 21:01 · javascript ·  · 0评论

因此,这些年来,我终于停止了drag脚,决定“适当”学习JavaScript。语言设计中最令人头疼的元素之一是继承的实现。拥有Ruby的经验,我很高兴看到闭包和动态类型。但是对于我一生来说,无法弄清楚使用其他实例进行继承的对象实例将带来什么好处。

我知道这个答案要晚3年了,但是我真的认为当前的答案不能提供足够的信息来说明原型继承比经典继承更好

首先,让我们看一下JavaScript程序员为捍卫原型继承而声明的最常见参数(我将从当前的答案库中获取这些参数):

  1. 这很简单。
  2. 功能强大。
  3. 它导致更小的,更少的冗余代码。
  4. 它是动态的,因此对于动态语言来说更好。

现在这些论点都是有效的,但是没有人愿意去解释原因。这就像告诉孩子学习数学很重要。当然可以,但是孩子当然不在乎;并不能通过说这很重要来使孩子喜欢数学。

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

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

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

我们将它作为Java的弟弟推销,因为像Visual Basic这样的补充语言当时是Microsoft语言家族中的C ++。

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

来自Java之类的具有经典继承性的语言的人们变得更加困惑,因为尽管构造函数看起来像类,但它们的行为却不像类。正如道格拉斯·克罗克福德所说:

这种间接作用旨在使经过经典训练的程序员对这种语言更加熟悉,但是却没有做到这一点,正如我们从Java程序员对JavaScript的极低评价中可以看到的那样。JavaScript的构造器模式对古典人群没有吸引力。它还掩盖了JavaScript的真正原型性质。结果,很少有知道如何有效使用该语言的程序员。

你有它。直接从马的嘴巴。

真正的原型继承

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

  1. 创建一个全新的对象。
  2. 克隆现有对象并对其进行扩展。

注意: JavaScript提供了两种克隆对象的方式-委托串联从今以后,我将使用“克隆”一词专门指代通过委派的继承,而使用“复制”一词专门指代通过级联的继承。

聊够了。让我们看一些例子。假设我有一个半径范围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提供了更好的委托方式Object.create函数用于执行以下操作:

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

就这样。您只是在JavaScript中进行了原型继承。那不是那么简单吗?您拿了一个对象,对其进行克隆,更改所需的内容,然后嘿,您便拥有了一个全新的对象。

现在您可能会问:“这有多简单?每次我要创建一个新圆时,都需要克隆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中的原型继承

如果您在上述程序中注意到该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中的构造函数模式是倒置的原型模式。无需创建对象,而是创建构造函数。new关键字结合this构造内部指针的一个克隆prototype的构造的。

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

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

还有许多其他原因应避免使用JavaScript中的构造函数模式。您可以在我的博客文章中阅读有关它们的信息:构造函数与原型


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

1.原型继承很简单

CMS在他的回答中指出:

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

让我们考虑一下我们刚刚做了什么。我们创建了circle一个半径为的对象5然后我们对其进行克隆,并将克隆的半径设置为10

因此,我们只需要两件事就可以使原型继承工作:

  1. 一种创建新对象的方法(例如,对象文字)。
  2. 扩展现有对象的方法(例如Object.create)。

相比之下,经典继承要复杂得多。在经典继承中,您可以:

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

你明白了。关键是原型继承更容易理解,更容易实现和更容易推理。

正如史蒂夫·耶格(Steve Yegge)在他的经典博客文章“ N00b的肖像”中所说

元数据是其他任何形式的描述或模型。代码中的注释只是对计算的自然语言描述。使元数据成为元数据的原因在于它并非绝对必要。如果我的狗有一些血统书,而我却丢失了文件,那么我仍然有一只非常有效的狗。

从同样的意义上讲,类只是元数据。继承不是严格要求的类。但是,有些人(通常为n00bs)发现使用此类课程更舒适。这给他们一种虚假的安全感。

好吧,我们也知道静态类型只是元数据。它们是针对两种读者的一种特殊的注释:程序员和编译器。静态类型讲述了有关计算的故事,大概是为了帮助两个读者群体了解程序的意图。但是静态类型可以在运行时扔掉,因为最后它们只是风格化的注释。他们就像家谱的文书工作:也许会使某种不安全的性格类型对他们的狗更快乐,但是狗当然不在乎。

如前所述,类给人一种错误的安全感。例如NullPointerException,即使您的代码清晰易读,您在Java中也会收到太多我发现经典继承通常会妨碍编程,但也许那只是Java。Python有一个了不起的经典继承系统。

2.原型继承是强大的

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

  1. 私有变量。
  2. 多重继承。

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

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

  1. 委托或差异继承
  2. 克隆或串联继承

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

当然,许多程序员并不认为这是真正的继承,因为instanceofisPrototypeOf另作说明。但是,可以通过在通过串联从原型继承的每个对象上存储一系列原型,来轻松地纠正这种情况:

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

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

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

但是,在JavaScript中,您很可能永远不会听说钻石问题,因为您可以精确控制要继承的属性以及从哪些原型继承的属性。

3.原型继承较少冗余

这一点很难解释,因为经典继承不一定会导致更多的冗余代码。实际上,无论是传统继承还是原型继承,都可以使用继承来减少代码中的冗余。

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

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

您必须在这里喜欢Java的“请一式四份签名”级别。

再一次,我认为那仅仅是因为Java很烂。

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

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

4.原型继承是动态的

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

在经典继承中这是不可能的,因为一旦创建了一个类,您将无法在运行时对其进行修改。与经典继承相比,这可能是原型继承的最大优势,应该将它放在首位。但是,我喜欢为最终目的尽力而为。

结论

原型继承很重要。对于JavaScript程序员,为什么要放弃原型继承的构造函数模式而转而使用原型继承的原型模式,这一点很重要。

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

使用原型模式解释原型继承不仅更加容易,而且还将使更好的程序员成为可能。

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

请允许我实际内联回答问题。

原型继承具有以下优点:

  1. 它更适合动态语言,因为继承与环境一样是动态的。(对JavaScript的适用性在这里应该很明显。)这使您可以快速进行操作,就像自定义类,而无需大量的基础结构代码。
  2. 与经典的类/对象二分法相比,实现原型对象方案要容易得多。
  3. 它消除了对象模型周围复杂的尖锐边缘的需要,例如“元类”(我从来没有喜欢过的元类……对不起!)或“特征值”之类。

但是,它具有以下缺点:

  1. 对原型语言进行类型检查不是不可能的,但这是非常非常困难的。原型语言的大多数“类型检查”是纯运行时“鸭子类型”样式检查。这并不适合所有环境。
  2. 同样,通过静态(或通常甚至是动态!)分析来优化方法分配等操作也很困难。可以(我强调:可以)非常容易地效率很低。
  3. 类似地,在原型语言中,对象创建可能(通常通常)比在更常规的类/对象二分方案中要慢得多。

我认为您可以在上述两行之间进行阅读,并提出传统类/对象方案的相应优点和缺点。当然,每个区域中都有更多,所以我将其余部分留给其他人回答。

IMO原型继承的主要好处是它的简单性。

语言的原型性质可能会使受过经典训练的人们感到困惑,但是事实证明,这实际上是一个非常简单而强大的概念,即差异继承

您不需要进行分类,您的代码更小,更少冗余,对象从其他更通用的对象继承。

如果您以原型方式思考,您很快就会发现您不需要课程...

原型继承将在不久的将来更加流行,ECMAScript 5th Edition规范引入了该Object.create方法,该方法使您可以生成一个新的对象实例,该对象实例可以以非常简单的方式从另一个实例继承:

var obj = Object.create(baseInstance);

所有浏览器供应商都在实施该标准的新版本,我认为我们将开始看到更多的纯原型继承...

两种方法之间确实没有太多选择。要掌握的基本思想是,当为JavaScript引擎赋予要读取的对象的属性时,它首先检查实例,如果缺少该属性,它将检查原型链。这是显示原型和古典之间的区别的示例:

原型

var single = { status: "Single" },
    princeWilliam = Object.create(single),
    cliffRichard = Object.create(single);

console.log(Object.keys(princeWilliam).length); // 0
console.log(Object.keys(cliffRichard).length); // 0

// Marriage event occurs
princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)

经典的实例方法 (效率低下,因为每个实例都存储自己的属性)

function Single() {
    this.status = "Single";
}

var princeWilliam = new Single(),
    cliffRichard = new Single();

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 1

高效古典

function Single() {
}

Single.prototype.status = "Single";

var princeWilliam = new Single(),
    cliffRichard = new Single();

princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 0
console.log(cliffRichard.status); // "Single"

如您所见,由于可以操纵以经典样式声明的“类”的原型,因此使用原型继承确实没有任何好处。它是经典方法的子集。

Web开发:原型继承与经典继承

http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html

经典与原型继承-代码日志

古典与原型继承

本文地址:http://javascript.askforanswer.com/yuanxingjichengxiangduiyujingdiandehaochu.html
文章标签: ,   ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!