有人能解释一下JavaScript原型继承吗?

7

我想知道有没有人能够友好地解释一下OO JavaScript中的function.prototype

我来自服务器端编程背景,也许我还没有完全掌握原型的整个概念,

给定以下代码片段:

var animate=function(){};
animate.angular=function(){/*does something here*/};
animate.circular=function(){/*does something here*/};

并且
var animate=function(){};
animate.prototype.angular=function(){/*does something here*/};
animate.prototype.circular=function(){/*does something here*/};

据我所知,这两个函数都可以通过animate.angular(/*args*/)animate.circular(/*args*/)进行调用。那么,我的问题是,第二种方式定义函数的优点是什么?它们有何不同?希望我的问题表述清楚了...
编辑:非常感谢大家给出的启示性答案。在这里很难判断哪个答案是“正确”的,所以我会选择我认为对此做出了最大贡献的答案。
你们肯定让我有更多的思考...

3
以下是一篇对于原型继承(尤其是关于JavaScript)的详细解释,这是一个很好的答案:https://dev59.com/9nI-5IYBdhLWcg3w9tdw#1598077 - Crescent Fresh
2
参见:https://dev59.com/sXVC5IYBdhLWcg3wykQt - Crescent Fresh
6个回答

6

我想您在示例中的某处意思是将某些内容设置为new animate()。如果不使用new,我稍微解释一下会发生什么:

var animate = function(){ console.log(0, 'animate'); };
animate.angular = function(){ console.log(1, 'animate.angular'); };
animate.circular = function(){ console.log(2, 'animate.circular'); };

animate.prototype.angular = function(){ console.log(3, 'animate.prototype.angular'); };
animate.prototype.circular = function(){ console.log(4, 'animate.prototype.circular'); };

只有前两个函数,#1和#2,可以从animate变量中调用。
animate.angular();
animate.circular();

如果您创建了一个新的animate(),则可以调用接下来的两个函数#3和#4(但不是#1或#2)。
var ani2 = new animate();

ani2.angular();
ani2.circular();

此外,animate()是一个函数,但ani2不是。
console.log(5, typeof animate);
console.log(6, typeof ani2);
console.log(7, animate());

虽然ani2已经被创建,但你可以通过animate.prototype向它添加新成员。
animate.prototype.bark = function(){ console.log(8, 'bark'); };
ani2.bark();

动画变量并不继承自它的原型。
console.log(9, typeof ani2.bark);
console.log(10, typeof animate.bark);

请注意,ani2并不继承直接应用于animate变量的成员。它只继承自animate.prototype。
animate.paperclip = function(){ console.log(11, "paperclip"); };

animate.paperclip();
console.log(12, typeof ani2.paperclip);
console.log(13, typeof animate.paperclip);

您还可以在构造函数中使用this关键字,例如animate,以向new子项添加实例成员。

var Anime = function(a,b){ this.a=a; this.b=b; this.c=console; };
var anime1 = new Anime(14, 'anime1');
var anime2 = new Anime(15, 'anime2');
anime1.c.log(anime1.a, anime1.b);
anime2.c.log(anime2.a, anime2.b);

Anime.prototype.a = 16;
Anime.prototype.z = 'z';

var anime3 = new Anime(17, 'anime3');
anime3.c.log(18, anime3.a, anime3.b, anime3.z, " ", anime2.a, anime2.b, anime2.z, " ", anime1.a, anime1.b, anime1.z);
anime2.z='N';
anime3.c.log(19, anime3.a, anime3.b, anime3.z, " ", anime2.a, anime2.b, anime2.z, " ", anime1.a, anime1.b, anime1.z);

因为被修改,为anime2.z分配了一个单独的内存实例,而anime1和anime3仍然共享一个节俭的未修改的z。

a、b和c成员不是以同样的方式“公共”的。它们是在构造函数中立即使用thisnew Anime()(而不是从Anime.prototype继承)分配的。另外,原型上的a成员总是由构造函数“个性化”的。

永远不要忘记new关键字,否则它将不能正常工作。例如,在没有使用new调用的构造函数中,this指向全局对象。

console.log(20, typeof window.a, typeof window.b, typeof window.c);
var opps = Anime(21, 'zapp');
console.log(22, typeof window.a, typeof window.b, typeof window.c);
console.log(23, typeof opps);

这是输出结果。Tom建议的Douglas Crockford视频也很不错!


/*
1 animate.angular
2 animate.circular
0 animate
3 animate.prototype.angular
4 animate.prototype.circular
5 function
6 object
0 animate
7 undefined
8 bark
9 function
10 undefined
11 paperclip
12 undefined
13 function
14 anime1
15 anime2
18 17 anime3 z 15 anime2 z 14 anime1 z
19 17 anime3 z 15 anime2 N 14 anime1 z
20 undefined undefined undefined
22 number string object
23 undefined
*/

你正在使用的一个广泛使用的约定是对构造函数使用大写字母作为首字母,这有助于不忘记 new 关键字。关于原型继承的好文章可以在这里找到:http://msdn.microsoft.com/en-us/magazine/ff852808.aspx - hugo der hungrige

3
虽然有时候感觉像是这样,但 JavaScript 实际上并没有类,而是使用原型。您可以定义一个原型,然后创建原型的副本。
从以下内容开始:
var animate=function(){};
animate.angular=function(){/*does something here*/};

您可以:

var a = new animate();
animate.angular();       // OK
a.circular();            // error: a.circular is not a function

然而,如果您从以下内容开始:
function animate(i){};
animate.prototype.angular = function() {};

现在您可以

var a = new animate();
a.angular();

当然,如果您有实例变量,这将更有趣。
function animate(i) {
    this.x = i;
}
animate.prototype.angular = function() {
    this.x *= 2;
}

var a = new animate(5);
a.angular();
console.log(a.x);    // 10

2

基于原型的面向对象编程比类式面向对象编程更加自由。

如果你真的想学习它,像许多人说的那样,阅读Crockford的解释,你不会有比这更好的资源。

如果你想要快速受益:

var Building = function() {
    this.openDoor = function() {};
};

var House = function() {
    this.closeDoor = function() {};
};
House.prototype = new Building();

var a = new House();
a.openDoor();
a.closeDoor();

下面这样定义对象(在其他语言中表示类的东西)实在是太糟糕了,所以我会在我的回答中包含一个小提示:

最好的方法是在你选择的全局命名空间下构建系统,例如:

if (typeof MYAPP === 'undefined' || !MYAPP) {
    var MYAPP = {};
}

function New(className, classBody) {
    // This adds your "classes" to this MYAPP namespace so they can be instantiated
    // You need some magic here, so have fun with this function
}

New('Building', function() {
    this.openDoor = function() {};
});

New('House', function() {
    this.prototype = new MYAPP.Building();
    this.closeDoor = function() {};
});

// Now you can do the same as before but your application is cleaner :)
var a = new MYAPP.House();
a.openDoor();
a.closeDoor();

欢呼。

2
JavaScript是一种奇怪的语言...与其他语言相比,它非常强大,但结构不是那么严格...
原型是JavaScript如何让你节省内存的方式,如果你将创建多个类实例...所以如果你以面向对象编程的方式使用JS,你应该将函数定义为原型的一部分。此外,有一些方法可以使用原型模拟继承。
我强烈推荐书籍《"Javascript, the Good Parts"》,其中有很多关于这方面的优秀解释。

还有一个同名的Google技术讲座:http://www.youtube.com/watch?v=hQVTIJBZook今天刚看了这个视频。 - Jon Homan
1
@gunderson: “奇怪”是一个非常主观的表达。在经过6个月的适当学习后,我发现自己用JS编程比以往使用Java或C++更加流畅和自然。因此,我不能同意你的观点。 - Tom Bartel
这就是为什么我喜欢MooTools,它以我欣赏的风格暴露了JavaScript继承。 - Ryan Florence
Tom Bartel:哦,我不是说JavaScript很奇怪。我喜欢它 :) - gunderson

2
如果你有三个小时的时间,我建议你观看YUI Theater中的“The JavaScript Programming Language”视频。演讲者/教师是Douglas Crockford,他将为你提供坚实的JS基础,以便你可以构建自己的知识体系。
问候,
汤姆

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接