所以,这些年来我最终停止了拖延,并决定“认真地”学习JavaScript。该语言设计中最令人困惑的元素之一是其继承的实现方式。虽然我有Ruby的经验,很高兴看到闭包和动态类型,但我却无法弄清楚使用其他实例进行继承的对象实例有什么好处。
所以,这些年来我最终停止了拖延,并决定“认真地”学习JavaScript。该语言设计中最令人困惑的元素之一是其继承的实现方式。虽然我有Ruby的经验,很高兴看到闭包和动态类型,但我却无法弄清楚使用其他实例进行继承的对象实例有什么好处。
注意:我在10年前写下这个答案时,认为继承是有害的。在软件开发中没有任何好理由使用继承。你可以用组合来实现继承的所有功能,并且做得更好。考虑使用代数数据类型。
这很糟糕,因为当人们在JavaScript中使用构造函数时,他们认为构造函数从其他构造函数继承。这是错误的。在原型继承中,对象从其他对象继承。构造函数从未涉及其中。这是大多数人感到困惑的地方。当时我们把它推销成Java的小弟弟,就像Visual Basic在微软语言家族中是C++的补充语言一样。
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;
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);
你有一个想法。原型继承更易于理解、实现和推理。
正如史蒂夫·耶吉在他的经典博客文章“ N00b的画像”中所说:
元数据是对其他内容的任何一种描述或模型。代码中的注释只是计算的自然语言描述。使元数据成为元数据的是它不是严格必需的。如果我有一只有族谱文件的狗,而我失去了文件,我仍然有一只完全有效的狗。
同样地,类只是元数据。类并非继承所必需的。然而,有些人(通常是新手)发现使用类更舒适。这给了他们一种虚假的安全感。
我们也知道静态类型只是元数据,它们是专门针对两类读者的一种特殊注释:程序员和编译器。静态类型讲述了计算的故事,以帮助这两个读者群体理解程序的意图。但是在运行时可以丢弃静态类型,因为它们最终只是样式化的注释。它们就像家谱文件:它可能会让某些不安全的人格类型对他们的狗更满意,但狗肯定不在乎。_.extend
就是这样做的。instanceof
和isPrototypeOf
说另外一种情况。但是,通过将原型的数组存储在每个通过串联继承自原型的对象上,可以很容易地解决这个问题。function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Java因此行为而臭名昭著。我清楚地记得Bob Nystrom在他关于Pratt Parsers的博客文章中提到了以下轶事:
你得喜欢Java的“请用四份副本签字”的官僚主义水平。
再次说明,我认为这只是因为Java太糟糕了。
一个有效的论点是,并非所有支持经典继承的语言都支持多重继承。Java再次出现在脑海中。是的,Java有接口,但这并不足够。有时你真的需要多重继承。
由于原型继承允许多重继承,如果使用原型继承编写需要多重继承的代码,那么比使用具有经典继承但没有多重继承的语言更少冗余。
让我来直接回答这个问题。
原型继承有以下优点:
然而,它也有以下缺点:
我认为您可以从上面的文字之间读出传统类/对象方案的相应优缺点。当然,每个领域都有更多的优势和劣势,所以我会把剩下的内容留给其他回答者。
在原型继承中,其主要优点在于其简单性。
该语言的原型性质可能会让受过经典训练的人感到困惑,但事实证明这实际上是一个非常简单而强大的概念,差异化继承。
您不需要进行分类,代码更小,更少冗余,对象从其他更通用的对象继承。
如果您从原型的角度来看待问题,很快就会发现您不需要类...
原型继承将在不久的将来变得更加流行,ECMAScript第5版规范引入了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开发:原型继承 vs. 经典继承
http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html
经典继承与原型继承 - Stack Overflow
Object.create
会创建一个具有指定原型的新对象。你的措辞让人误以为原型被克隆了。 - Pavel Horalconstructors
或者Object.create
正在发生的事情。没有复制正在进行。现在回答的措辞存在更多混淆的风险。 - Andy E