因此,这些年来,我终于不再拖延脚步,决定“适当”学习JavaScript。语言设计最令人头疼的元素之一就是继承的实现。拥有Ruby的经验,我很高兴看到闭包和动态类型。但是对于我一生来说,无法弄清楚使用其他实例进行继承的对象实例将带来什么好处。
因此,这些年来,我终于不再拖延脚步,决定“适当”学习JavaScript。语言设计最令人头疼的元素之一就是继承的实现。拥有Ruby的经验,我很高兴看到闭包和动态类型。但是对于我一生来说,无法弄清楚使用其他实例进行继承的对象实例将带来什么好处。
Answers:
我知道这个答案要晚3年了,但是我真的认为当前的答案不能提供足够的信息来说明原型继承比经典继承更好。
首先,让我们看一下JavaScript程序员为捍卫原型继承而声明的最常见的参数(我从当前的答案库中获取这些参数):
现在这些论点都是有效的,但是没有人愿意去解释原因。这就像告诉孩子学习数学很重要。当然可以,但是孩子当然不在乎;并不能通过说这很重要来使孩子喜欢数学。
我认为原型继承的问题在于它是从JavaScript的角度进行解释的。我喜欢JavaScript,但是JavaScript的原型继承是错误的。与经典继承不同,原型继承有两种模式:
不幸的是,JavaScript使用原型继承的构造函数模式。这是因为创建JavaScript时,Brendan Eich(JS的创建者)希望它看起来像Java(具有经典继承):
我们把它作为Java的弟弟推销,因为像Visual Basic这样的补充语言当时是Microsoft语言家族中的C ++。
这很糟糕,因为当人们在JavaScript中使用构造函数时,他们会想到从其他构造函数继承的构造函数。错了 在原型继承中,对象从其他对象继承。构造函数永远不会出现。这就是让大多数人困惑的地方。
来自Java之类的具有经典继承性的语言的人们变得更加困惑,因为尽管构造函数看起来像类,但它们的行为却不像类。正如道格拉斯·克罗克福德所说:
这种间接作用旨在使经过经典训练的程序员对这种语言更加熟悉,但是这样做却没有做到,正如我们从Java程序员对JavaScript的极低见解中可以看到的那样。JavaScript的构造器模式对古典人群没有吸引力。它还掩盖了JavaScript的真正原型性质。结果,很少有知道如何有效使用该语言的程序员。
你有它。直接从马的嘴巴。
原型继承是关于对象的。对象从其他对象继承属性。这里的所有都是它的。有两种使用原型继承创建对象的方法:
注意: 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);
如果您在上述程序中注意到该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中的构造函数模式。您可以在我的博客文章中阅读有关它们的信息:构造函数与原型
那么,原型继承比经典继承有什么好处?让我们再次审视最常见的论点,并解释原因。
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);
}
因此,原型继承与经典继承一样强大。实际上,它比经典继承强大得多,因为在原型继承中,您可以从不同的原型中手动选择要复制的属性和要忽略的属性。
在经典继承中,不可能(或至少非常困难)选择要继承的属性。他们使用虚拟基类和接口来解决菱形问题。
但是,在JavaScript中,您很可能永远不会听说钻石问题,因为您可以精确控制要继承的属性以及从哪些原型继承的属性。
这一点很难解释,因为经典继承不一定会导致更多的冗余代码。实际上,无论是经典继承还是原型继承,都可以用来减少代码中的冗余。
一种说法可能是,大多数具有经典继承性的编程语言都是静态类型的,并且要求用户显式声明类型(与具有隐式静态类型的Haskell不同)。因此,这导致了更冗长的代码。
Java因这种行为而臭名昭著。我清楚地记得Bob Nystrom在他关于Pratt Parsers的博客文章中提到了以下轶事:
您一定会喜欢Java的官僚机构的“请一式四份签名”级别。
同样,我认为那只是因为Java很烂。
一个有效的论据是,并非所有具有经典继承的语言都支持多重继承。再一次想到Java。是的,Java有接口,但这还不够。有时您确实需要多重继承。
由于原型继承允许多重继承,因此,如果使用原型继承而不是使用具有经典继承但没有多重继承的语言编写,则需要多重继承的代码将减少冗余。
原型继承的最重要优点之一是,可以在原型创建后向其添加新属性。这使您可以向原型添加新方法,该方法将自动提供给委派给该原型的所有对象。
在经典继承中这是不可能的,因为一旦创建了类,就无法在运行时对其进行修改。与经典继承相比,这可能是原型继承的最大优点,应该放在首位。但是,我喜欢尽全力以赴。
原型继承很重要。对于JavaScript程序员,为什么要放弃原型继承的构造函数模式而转而使用原型继承的原型模式,这一点很重要。
我们需要正确地开始讲授JavaScript,这意味着向新程序员展示如何使用原型模式而不是构造函数模式编写代码。
使用原型模式解释原型继承不仅更加容易,而且还将使更好的程序员成为可能。
如果您喜欢这个答案,那么您还应该阅读我的博客文章“ 为什么原型继承很重要 ”。相信我,您不会失望的。
Object.create
正在使用指定的原型创建新对象。您选择的单词会给人以原型被克隆的印象。
请允许我实际内联回答问题。
原型继承具有以下优点:
但是,它具有以下缺点:
我认为您可以在上述两行之间进行阅读,并提出传统类/对象方案的相应优点和缺点。当然,每个区域都有更多,因此我将其余部分留给其他人回答。
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
经典vs原型继承-代码日志