原型inheritance优于古典?

所以这些年我终于停下脚步,决定正确地学习JavaScript。 语言devise中最令人头疼的元素之一就是inheritance的实现。 有了Ruby的经验,我很高兴看到closures和dynamicinput; 但是对于我的生活来说,无法弄清楚使用其他实例进行inheritance的对象实例有什么好处。

我知道这个答案迟了3年,但我真的认为目前的答案没有提供足够的信息, 说明原型inheritance如何比传统inheritance更好 。

首先让我们看看最常见的参数JavaScript程序员在原型inheritance的防御状态(我从当前的答案池中拿出这些参数):

  1. 这很简单。
  2. 它很强大。
  3. 它导致更小,更less冗余的代码。
  4. 它是dynamic的,因此它对dynamic语言更好。

现在这些论点都是有效的,但是没有人解释为什么。 这就像告诉孩子学习math很重要。 当然是,但孩子当然不在乎; 你不能像“math”这样的孩子说这很重要。

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

  1. 原型遗传的原型模式。
  2. 原型inheritance的构造器模式。

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

我们把它当作Java的一个小兄弟来推动,因为像Visual Basic这样的补充语言是当时微软语言家族中的C ++。

这很糟糕,因为当人们在JavaScript中使用构造函数时,他们会想到从其他构造函数inheritance的构造函数。 这是错误的。 在原型inheritance中,对象从其他对象inheritance。 施工人员从来没有进入这个图景。 这是大多数人混淆。

像Java这样的具有经典inheritance的语言的人会变得更加困惑,因为尽pipe构造函数看起来像类,但它们并不像类那样工作。 正如道格拉斯·克罗克福德所言:

这种间接的目的是使语言对经过培训的程序员来说更加熟悉,但是却没有这样做,正如我们从Java程序员对JavaScript的看法很低时就可以看到的那样。 JavaScript的构造模式并没有吸引古典人群。 它也掩盖了JavaScript的真实原型性质。 因此,很less有程序员知道如何有效地使用语言。

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

真正的原型inheritance

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

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

注意: JavaScript提供了两种克隆对象的方式 – 委派和连接 。 今后,我将使用“克隆”一词来指代通过委托来inheritance,而“复制”一词则专指通过连接inheritance。

说够了。 我们来看一些例子。 说我有一个半径为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; }; 

现在我想创build另一个半径为10圆。 一种方法是:

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

不过JavaScript提供了一个更好的方式 – 委托 。 Crockford的Object.create函数用于执行此操作:

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

就这样。 你只是在JavaScript中做了原型inheritance。 不是那么简单吗? 你拿一个东西,克隆它,改变你需要的任何东西,然后嘿嘿 – 你给自己一个全新的东西。

现在你可能会问,“这是如何简单?每当我想创build一个新的圆,我需要克隆circle ,手动分配一个半径”。 那么解决scheme就是使用一个函数为你做繁重的工作:

 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中的原型inheritance

如果你注意到在上面的程序中create函数创build一个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中的构造函数模式是倒置的原型模式。 而不是创build一个对象,你创build一个构造函数。 new关键字将构造函数中的this指针绑定到构造函数prototype的一个克隆上。

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

而不是思考从其他对象inheritance的对象,他们想到构造函数从其他构造函数inheritance,然后变得完全混淆。

还有一大堆其他原因,为什么JavaScript中的构造函数模式应该避免。 你可以在我的博客文章阅读关于他们: 构造与原型


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

1.原型inheritance简单

CMS在他的回答中指出:

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

让我们考虑一下我们刚刚做了什么。 我们创build了一个半径为5的物体circle 。 然后我们克隆它,并给克隆半径10

因此,我们只需要两件事情来做原型inheritance工作:

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

相比之下,古典的inheritance要复杂得多。 在经典的inheritance中,你有:

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

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

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

元数据是任何其他types的描述或模型。 代码中的注释只是计算的自然语言描述。 元数据元数据是什么使得它不是绝对必要的。 如果我有一只带有谱系文书的狗,而且我丢了文书,我还是有一只完全有效的狗。

在同样的意义上,类只是元数据。 类不是严格要求inheritance。 然而,有些人(通常是n00bs)find更舒适的课程。 这给了他们一种虚假的安全感。

那么我们也知道静态types只是元数据。 它们是针对两种读者的专门的评论:程序员和编译器。 静态types讲述了一个关于计算的故事,大概是为了帮助读者群体理解程序的意图。 但是静态types可以在运行时被抛弃,因为最终它们只是风格化的注释。 它们就像谱系文书:它可能会使某种不安全的人格types对他们的狗感到快乐,但是狗当然不在乎。

正如我刚才所说,阶级给人一种错误的安全感。 例如,即使代码完全清晰,Java中也会得到太多的NullPointerExceptionexception。 我发现经典inheritance通常会阻碍编程,但也许这只是Java。 Python有一个惊人的古典inheritance系统。

2.原型inheritance是强大的

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

  1. 私有variables。
  2. 多重inheritance。

这个说法是错误的。 我们已经知道JavaScript 通过闭包支持私有variables ,但是多重inheritance呢? JavaScript中的对象只有一个原型。

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

  1. 委派或差异inheritance
  2. 克隆或连锁遗传

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

当然,许多程序员不认为这是真正的inheritance,因为instanceofisPrototypeOf否则。 但是,通过在通过串联inheritance原型的每个对象上存储原型数组,可以很容易地解决这个问题。

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

因此,原型inheritance与经典inheritance一样强大。 事实上,它比经典inheritance更强大,因为在原型inheritance中,您可以select要复制哪些属性以及从不同原型中省略哪些属性。

在经典的inheritance中,select你想要inheritance的属性是不可能的(或者至less非常困难)。 他们使用虚拟基类和接口来解决钻石问题 。

然而,在JavaScript中,你很可能从来没有听说过钻石问题,因为你可以准确地控制你想要inheritance的属性和原型。

3.原型inheritance不再冗余

这一点有点难以解释,因为经典的inheritance不一定会导致更多的冗余代码。 事实上,遗传,无论是古典还是原型,都被用来减less代码冗余。

一个参数可能是,大多数具有经典inheritance的编程语言是静态types的,并且要求用户显式声明types(与具有隐式静态types的Haskell不同)。 因此这导致更详细的代码。

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

你一定要喜欢爪哇的“请在这里一式四份”的官僚层面签字。

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

一个有效的论点是,并不是所有具有经典inheritance的语言都支持多重inheritance。 再想起Java。 是的,Java有接口,但这还不够。 有时你真的需要多重inheritance。

由于原型inheritance允许多重inheritance,因此如果使用原型inheritance进行编写,而不是使用具有经典inheritance但不具有多重inheritance的语言,则需要多重inheritance的代码不会多余。

4.原型inheritance是dynamic的

原型inheritance的一个最重要的优点是可以在创build原型后添加新的属性。 这使您可以将新方法添加到原型中,原型将自动提供给委托给该原型的所有对象。

这在经典inheritance中是不可能的,因为一旦创build了类,就不能在运行时对其进行修改。 这可能是原型inheritance优于传统inheritance的最大优势,应该是最高的。 不过,我喜欢最好的保存。

结论

原型遗传很重要。 教育JavaScript程序员为什么要抛弃原型inheritance的构造模式,转而支持原型inheritance的原型模式,这一点很重要。

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

使用原型模式解释原型inheritance不仅更容易,而且还会使程序员变得更好。

如果你喜欢这个答案,那么你也应该阅读我的博客文章“ 为什么原型inheritance问题 ”。 相信我,你不会失望的。

请允许我实际回答这个问题。

原型inheritance具有以下优点:

  1. 它更适合于dynamic语言,因为inheritance和它所处的环境一样dynamic。(这里JavaScript的适用性应该是显而易见的)。这允许你快速地做事情,就像定制没有大量基础结构代码的类。
  2. 实现原型对象scheme比传统的类/对象二分法更容易。
  3. 它消除了像“元类”(我从来没有metaclass我喜欢…对不起!)或“特征值”等对象模型周围复杂的尖锐边缘的需要。

但是它有以下缺点:

  1. types检查原型语言并不是不可能的,但是非常非常困难。 大多数原型语言的“types检查”是纯粹的运行时“鸭子打字”式检查。 这不适合所有的环境。
  2. 用静态(或者甚至是dynamic的!)分析来优化方法调度这样的事情同样是困难的。 它可以 (我强调: 可以 )非常容易地非常低效。
  3. 类似地,对象创build可以(通常)在原型语言中比在更传统的类/对象二分法中更慢。

我认为你可以在上面这两行之间进行阅读,并拿出传统的类/对象scheme的相应优缺点。 当然,每个区域都有更多的东西,所以我会把剩下的东西留给其他人来回答。

IMO原型inheritance的主要好处是其简单性。

语言的原型本质可能会混淆那些受过古典训练的人,但实际上这是一个非常简单而强大的概念, 差异inheritance 。

您不需要进行分类 ,代码更小,冗余更less,对象从其他更一般的对象inheritance。

如果你认为原型,你很快就会注意到你不需要上课…

原型inheritance将会在不久的将来更受欢迎, ECMAScript第5版规范引入了Object.create方法,该方法允许您以一种非常简单的方式生成一个新的对象实例:

 var obj = Object.create(baseInstance); 

这个标准的新版本正在被所有的浏览器供应商实现,我想我们将开始看到更纯的原型inheritance…

这两种方法之间确实没有太多的select。 要掌握的基本思想是,当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" 

正如你所看到的,由于可以操纵古典风格中声明的“类”的原型,所以使用原型inheritance是没有任何好处的。 这是经典方法的一个子集。

Web开发:原型inheritance与古典inheritance

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

经典VS原型inheritance – Stack Overflow

古典与原型的inheritance

在Javascript中,与原型模式,你不能做instanceOf。 与古典模型,你可以。 我打算在这里给你一个链接。 http://www.objectplayground.com谈到Javascript原型的最好的video。;

随着ECMA 6的到来,Javascript将支持新的类语法。 所以TRUE经典模型即将到来。

未来是坚持古典模式。