所以这些年来我终于停止了拖延,决定“正确地”学习JavaScript。该语言设计中最令人头疼的元素之一是它的继承实现。有Ruby的经验,我真的很高兴看到闭包和动态类型;但是对于我来说,我不知道从对象实例使用其他实例来继承有什么好处。
Web开发:原型继承与经典继承
http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html
经典Vs原型继承-堆栈溢出
经典Vs原型继承
请允许我直接回答这个问题。
原型继承具有以下优点:
It is better suited to dynamic languages because the inheritance is as dynamic as the environment it is in. (The applicability to JavaScript should be obvious here.) This permits you to do things quickly on the fly like customizing classes without huge amounts of infrastructure code. It is easier to implement a prototyping object scheme than the classic class/object dichotomy schemes. It eliminates the need for the complex sharp edges around the object model like "metaclasses" (I never metaclass I liked... sorry!) or "eigenvalues" or the like.
但它有以下缺点:
Type checking a prototype language isn't impossible, but it's very, very difficult. Most "type checking" of prototypical languages is pure run-time "duck typing"-style checks. This is not suitable to all environments. It is similarly difficult to do things like optimizing method dispatch by static (or, often, even dynamic!) analysis. It can (I stress: can) be very inefficient very easily. Similarly object creation can be (and usually is) much slower in a prototyping language than it can be in a more conventional class/object dichotomy scheme.
我认为您可以从上面的字里行间看出传统类/对象方案的相应优点和缺点。当然,每个领域都有更多的问题,所以我把剩下的留给其他人来回答。
在我看来,原型继承的主要好处是它的简单性。
这种语言的原型性质可能会让受过经典训练的人感到困惑,但事实证明,这实际上是一个非常简单而强大的概念,差分继承。
你不需要进行分类,你的代码更小,更少冗余,对象从其他对象继承,更一般的对象。
如果你认为原型化,你很快就会发现你不需要类……
原型继承在不久的将来会更加流行,ECMAScript第5版规范引入了对象。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"
正如您所看到的,由于可以操作以经典风格声明的“类”的原型,因此使用原型继承实际上没有任何好处。它是经典方法的一个子集。
注:从我10年前写这个答案开始,我就知道了遗传是有害的。在软件开发中使用继承是没有理由的。继承能做的所有事情,复合也能做得更好。请考虑使用代数数据类型。
我知道这个答案晚了3年,但我真的认为目前的答案没有提供足够的信息说明原型继承比经典继承更好。
首先让我们看看JavaScript程序员在捍卫原型继承时最常见的参数(我从当前的答案池中选取了这些参数):
这很简单。 这是强大的。 它会导致更小、更少冗余的代码。 它是动态的,因此更适合动态语言。
现在这些论点都是正确的,但是没有人费心解释为什么。这就像告诉孩子学习数学很重要一样。当然是这样,但孩子肯定不在乎;你不能光说数学很重要就让一个孩子喜欢它。
我认为原型继承的问题在于它是从JavaScript的角度来解释的。我喜欢JavaScript,但是JavaScript中的原型继承是错误的。与经典继承不同,原型继承有两种模式:
原型继承的原型模式。 原型继承的构造函数模式。
不幸的是,JavaScript使用原型继承的构造函数模式。这是因为当JavaScript被创建时,Brendan Eich (JS的创造者)希望它看起来像Java(具有经典继承):
我们把它作为Java的小兄弟,作为一种补充语言,就像Visual Basic是当时微软语系中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提供了一种更好的方式——委托。对象。Create函数用于执行以下操作:
var circle2 = Object.create(circle);
circle2.radius = 10;
这是所有。你只是用JavaScript做了原型继承。这不是很简单吗?你拿一个对象,克隆它,改变任何你需要的东西,嘿,瞬间-你得到了一个全新的对象。
现在你可能会问,“这有多简单?每次我想要创建一个新的圆,我需要克隆圆,并手动分配它的半径”。好吧,解决方案是使用一个函数来为你做繁重的工作:
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函数创建了一个克隆的圆,分配一个新的半径,然后返回它。这正是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指针绑定到构造函数原型的克隆。
听起来令人困惑?这是因为JavaScript中的构造函数模式不必要地复杂化了事情。这是大多数程序员难以理解的。
他们认为构造函数继承自其他构造函数,而不是从其他对象继承对象,这样就完全搞混了。
那么,与经典继承相比,原型继承的好处是什么呢?让我们再看一遍最常见的论点,并解释原因。
1. 原型继承很简单
CMS在他的回答中说:
在我看来,原型继承的主要好处是它的简单性。
让我们考虑一下刚刚做了什么。我们创建了一个半径为5的对象圆。然后我们克隆了它,让克隆体的半径为10。
因此,我们只需要两个东西来使原型继承工作:
一种创建新对象的方法(例如对象字面量)。 扩展现有对象的方法(例如object .create)。
相比之下,经典的继承要复杂得多。在经典遗传中有:
类。 对象。 接口。 抽象类。 最后一课。 虚基类。 构造函数。 析构函数。
你懂的。关键是原型继承更容易理解,更容易实现,也更容易推理。
正如Steve Yegge在他的经典博客文章“N00b的画像”中所说:
元数据是其他事物的任何类型的描述或模型。代码中的注释只是计算的自然语言描述。元数据之所以是元数据,是因为它不是严格必要的。如果我有一只狗,有一些血统文件,我失去了文件,我仍然有一只完全有效的狗。
在同样的意义上,类只是元数据。继承并不严格要求类。然而,有些人(通常是n00b)发现类更适合使用。这给了他们一种虚假的安全感。
Well, we also know that static types are just metadata. They're a specialized kind of comment targeted at two kinds of readers: programmers and compilers. Static types tell a story about the computation, presumably to help both reader groups understand the intent of the program. But the static types can be thrown away at runtime, because in the end they're just stylized comments. They're like pedigree paperwork: it might make a certain insecure personality type happier about their dog, but the dog certainly doesn't care.
如前所述,课程给人一种虚假的安全感。例如,即使您的代码非常易读,您也会在Java中获得太多的nullpointerexception。我发现经典的继承通常会阻碍编程,但这可能只是Java的问题。Python有一个惊人的经典继承系统。
2. 原型继承功能强大
大多数来自经典背景的程序员认为经典继承比原型继承更强大,因为它具有:
私有变量。 多重继承。
这种说法是错误的。我们已经知道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中,你可能从未听说过菱形问题,因为你可以精确地控制你希望继承哪些属性以及从哪些原型继承。
3.原型继承更少冗余
这一点有点难以解释,因为经典继承不一定会导致更多的冗余代码。事实上,继承,无论是经典的还是原型的,都是用来减少代码中的冗余的。
一个可能的论点是,大多数具有经典继承的编程语言都是静态类型的,并且要求用户显式声明类型(不像Haskell具有隐式静态类型)。因此,这会导致更冗长的代码。
Java因这种行为而臭名昭著。我清楚地记得Bob Nystrom在他的博客文章中提到了以下关于Pratt Parsers的轶事:
你一定会喜欢Java的“请签署一式四份”级别的官僚主义。
同样,我认为这只是因为Java太糟糕了。
一个有效的论点是,并非所有具有经典继承的语言都支持多重继承。我再次想到了Java。是的,Java有接口,但这还不够。有时确实需要多重继承。
由于原型继承允许多重继承,使用原型继承编写需要多重继承的代码比使用具有经典继承但没有多重继承的语言编写的代码更少冗余。
4. 原型继承是动态的
原型继承最重要的优点之一是,您可以在创建原型后向原型添加新属性。这允许你向原型中添加新方法,这些方法将自动对委托给该原型的所有对象可用。
这在经典继承中是不可能的,因为一旦创建了一个类,就不能在运行时修改它。这可能是原型继承相对于经典继承的最大优势,而且它应该是最重要的。然而,我喜欢把最好的留到最后。
结论
原型继承很重要。教育JavaScript程序员为什么要放弃原型继承的构造函数模式而选择原型继承的原型模式是很重要的。
我们需要开始正确地教授JavaScript,这意味着向新程序员展示如何使用原型模式而不是构造函数模式来编写代码。
使用原型模式不仅可以更容易地解释原型继承,而且还可以培养更好的程序员。
推荐文章
- 如何使用Jest测试对象键和值是否相等?
- 将长模板文字行换行为多行,而无需在字符串中创建新行
- 如何在JavaScript中映射/减少/过滤一个集?
- Bower: ENOGIT Git未安装或不在PATH中
- 添加javascript选项选择
- 在Node.js中克隆对象
- 为什么在JavaScript的Date构造函数中month参数的范围从0到11 ?
- 使用JavaScript更改URL参数并指定默认值
- getter和setter是糟糕的设计吗?相互矛盾的建议
- 在window.setTimeout()发生之前取消/终止
- 如何删除未定义和空值从一个对象使用lodash?
- 检测当用户滚动到底部的div与jQuery
- 在JavaScript中检查字符串包含另一个子字符串的最快方法?
- 检测视口方向,如果方向是纵向显示警告消息通知用户的指示
- ASP。NET MVC 3 Razor:在head标签中包含JavaScript文件