Pie*_*ten 264 javascript oop inheritance language-design prototype-programming
所以这些年来我终于停止了我的脚,并决定"正确"学习JavaScript.语言设计中最令人头疼的元素之一是它的继承实现.有Ruby经验,我很高兴看到闭包和动态打字; 但是对于我的生活来说,无法弄清楚使用其他实例进行继承的对象实例会带来什么好处.
Aad*_*hah 537
我知道这个答案已经晚了3年但我真的认为目前的答案并没有提供关于原型继承如何比经典继承更好的足够信息.
首先让我们看看JavaScript程序员在保护原型继承时所说的最常见的参数(我从当前的答案池中获取这些参数):
现在这些论点都是有效的,但是没有人为解释原因而烦恼.这就像告诉孩子学习数学很重要.当然可以,但孩子肯定不在乎; 而且你不能说像数学这样的孩子说这很重要.
我认为原型继承的问题在于它是从JavaScript的角度来解释的.我喜欢JavaScript,但JavaScript中的原型继承是错误的.与经典继承不同,有两种原型继承模式:
不幸的是,JavaScript使用原型继承的构造函数模式.这是因为在创建JavaScript时,Brendan Eich(JS的创建者)希望它看起来像Java(具有经典继承):
我们把它当作Java的小弟弟推动它,因为像Visual Basic这样的补充语言当时是微软语言家族的C++.
这很糟糕,因为当人们在JavaScript中使用构造函数时,他们会想到从其他构造函数继承的构造函数.这是错的.在原型继承中,对象继承自其他对象.构造函数永远不会出现.这让大多数人感到困惑.
来自像Java这样具有经典继承的语言的人变得更加困惑,因为尽管构造函数看起来像类,但它们的行为并不像类.正如道格拉斯·克罗克福德所说:
这种间接性旨在使语言对于经过专业训练的程序员来说更为熟悉,但却没有做到这一点,正如我们从Java程序员对JavaScript的非常低级的看法中看到的那样.JavaScript的构造函数模式并没有吸引经典人群.它还掩盖了JavaScript真正的原型性质.因此,很少有程序员知道如何有效地使用该语言.
你有它.直接从马的嘴里.
原型继承完全是关于对象的.对象从其他对象继承属性.这里的所有都是它的.有两种使用原型继承创建对象的方法:
注意: JavaScript提供了两种克隆对象的方法 - 委托和连接.从此以后,我将使用"clone"一词专门通过委托来引用继承,而单词"copy"则通过连接专门引用继承.
足够的谈话.我们来看一些例子.说我有一个半径圆5:
var circle = {
radius: 5
};
Run Code Online (Sandbox Code Playgroud)
我们可以从半径计算圆的面积和周长:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Run Code Online (Sandbox Code Playgroud)
现在我想创建另一个半径圆10.一种方法是:
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
Run Code Online (Sandbox Code Playgroud)
但JavaScript提供了一种更好的方式 - 委派.Crockford的Object.create功能用于执行此操作:
var circle2 = Object.create(circle);
circle2.radius = 10;
Run Code Online (Sandbox Code Playgroud)
就这样.您刚刚在JavaScript中进行了原型继承.那不简单吗?你拿一个物体,克隆它,改变你需要的东西,然后嘿嘿 - 你得到了一个全新的物体.
现在你可能会问,"这怎么这么简单?每次我想要创建一个新的圆圈,我需要克隆circle并手动为它指定半径".那么解决方案就是使用一个函数为你做繁重的工作:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
Run Code Online (Sandbox Code Playgroud)
实际上,您可以将所有这些组合成一个对象文字,如下所示:
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);
Run Code Online (Sandbox Code Playgroud)
如果您在上面的程序中注意到该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);
Run Code Online (Sandbox Code Playgroud)
JavaScript中的构造函数模式是反转的原型模式.您可以创建构造函数,而不是创建对象.的new关键字结合this构造内部指针的一个克隆prototype的构造的.
听起来很混乱?这是因为JavaScript中的构造函数模式不必要地使事情复杂化.这是大多数程序员难以理解的.
他们没有考虑从其他对象继承的对象,而是认为构造函数继承自其他构造函数,然后变得完全混淆.
还有很多其他原因可以避免JavaScript中的构造函数模式.您可以在我的博客文章中阅读它们:构造函数与原型
那么原型继承优于经典继承有什么好处呢?让我们再次讨论最常见的论点,并解释原因.
CMS在他的回答中说:
在我看来,原型继承的主要好处是它的简单性.
让我们考虑一下我们刚刚做了什么.我们创建了circle一个半径为的对象5.然后我们克隆它并给克隆半径为10.
因此,我们只需要两件事就可以使原型继承工作:
Object.create).相比之下,经典继承要复杂得多.在经典继承中,你有:
你明白了.关键是原型继承更容易理解,更容易实现,更容易推理.
正如Steve Yegge在他的经典博客文章" N00b的肖像 "中所说:
元数据是任何其他类型的描述或模型.代码中的注释只是计算的自然语言描述.元数据元数据的原因在于它并非绝对必要.如果我有一条带有一些谱系文书的狗,而我丢失了文书工作,我仍然有一只完全有效的狗.
在同样的意义上,类只是元数据.继承不严格要求类.然而,有些人(通常是n00bs)找到更舒适的课程.它给了他们一种虚假的安全感.
好吧,我们也知道静态类型只是元数据.它们是针对两种读者的专门评论:程序员和编译器.静态类型讲述了有关计算的故事,可能是为了帮助两个读者组理解程序的意图.但是静态类型可以在运行时抛弃,因为最终它们只是风格化的注释.他们就像是谱系文书工作:它可能会使某种不安全的性格类型对他们的狗更开心,但狗肯定不会在意.
正如我之前所说,课程给人一种虚假的安全感.例如NullPointerException,即使您的代码非常清晰,您在Java中也会得到太多.我发现经典继承通常会妨碍编程,但也许只是Java.Python有一个惊人的经典继承系统.
大多数来自古典背景的程序员认为经典继承比原型继承更强大,因为它具有:
这种说法是错误的.我们已经知道JavaScript 通过闭包支持私有变量,但是多重继承呢?JavaScript中的对象只有一个原型.
事实是原型继承支持从多个原型继承.原型继承只是意味着一个对象继承自另一个对象.实际上有两种方法可以实现原型继承:
是JavaScript只允许对象委托给另一个对象.但是,它允许您复制任意数量的对象的属性.例如_.extend就是这样.
当然,由于许多程序员不认为这是正确的继承instanceof和isPrototypeOf别的说法.但是,通过在每个通过串联继承原型的对象上存储原型数组,可以很容易地解决这个问题:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Run Code Online (Sandbox Code Playgroud)
因此,原型继承与经典继承一样强大.实际上它比经典继承更强大,因为在原型继承中,您可以手动选择要复制的属性以及从不同原型中省略哪些属性.
在经典继承中,选择要继承的属性是不可能的(或者至少是非常困难的).他们使用虚拟基类和接口来解决钻石问题.
然而,在JavaScript中,您很可能永远不会听到钻石问题,因为您可以精确控制要继承的属性以及原型.
这一点有点难以解释,因为经典继承不一定会导致更多的冗余代码.实际上,继承(无论是经典还是原型)用于减少代码中的冗余.
一个论点可能是大多数具有经典继承的编程语言是静态类型的,并且需要用户显式声明类型(与具有隐式静态类型的Haskell不同).因此,这会导致更详细的代码.
Java因这种行为而臭名昭着.我清楚地记得Bob Nystrom在他关于Pratt Parsers的博客文章中提到了以下轶事:
你必须喜欢Java的"请在一式四份签署"官僚程度.
再说一次,我认为这只是因为Java糟透了.
一个有效的论点是,并非所有具有经典继承的语言都支持多重继承.再次想到Java.是Java有接口,但这还不够.有时你真的需要多重继承.
由于原型继承允许多重继承,因此如果使用原型继承而不是使用具有经典继承但没有多重继承的语言编写,那么需要多继承的代码就不那么多了.
原型继承最重要的优点之一是您可以在创建原型后为其添加新属性.这允许您向原型添加新方法,该方法将自动提供给委托给该原型的所有对象.
这在经典继承中是不可能的,因为一旦创建了类,就无法在运行时修改它.这可能是原型继承优于经典继承的最大优势,它应该是最重要的.但是我喜欢最好的保存.
原型继承很重要.让JavaScript程序员了解为什么放弃原型继承的构造函数模式以支持原型继承的原型模式是很重要的.
我们需要开始正确地教授JavaScript,这意味着向新程序员展示如何使用原型模式而不是构造函数模式编写代码.
使用原型模式不仅可以更容易地解释原型继承,而且还可以使更好的程序员.
如果您喜欢这个答案,那么您还应该阅读我的博客文章" 为什么原型继承很重要 ".相信我,你不会失望的.
JUS*_*ION 39
请允许我实际回答内联问题.
原型继承具有以下优点:
但它有以下缺点:
我想你可以在上面的行之间进行阅读,并提出传统类/对象方案的相应优缺点.当然,每个区域都有更多,所以我将剩下的留给其他人回答.
CMS*_*CMS 28
IMO原型继承的主要好处是它的简单性.
语言的原型性质可以迷惑谁是人经典的训练,但事实证明,其实这是一个真正简单而强大的概念,差分继承.
您不需要进行分类,代码更小,冗余更少,对象继承自其他更通用的对象.
如果你原型思考,你很快就会注意到你不需要上课......
原型继承在不久的将来会更受欢迎,ECMAScript第5版规范引入了该Object.create方法,它允许您以一种非常简单的方式生成一个从另一个继承的新对象实例:
var obj = Object.create(baseInstance);
Run Code Online (Sandbox Code Playgroud)
所有浏览器供应商都在实施该标准的新版本,我认为我们将开始看到更纯粹的原型继承......
Noe*_*ams 10
这两种方法之间的选择真的不多.要掌握的基本思想是,当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)
Run Code Online (Sandbox Code Playgroud)
使用实例方法的经典 (效率低,因为每个实例都存储它自己的属性)
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
Run Code Online (Sandbox Code Playgroud)
高效的经典
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"
Run Code Online (Sandbox Code Playgroud)
如您所见,由于可以操作以古典风格声明的"类"原型,因此使用原型继承确实没有任何好处.它是经典方法的一个子集.