1
JavaScript类
2
类是什么?
3
许多刚接触编程的朋友都可能理解不了类,其实类是对我们这个现实世界的模拟,把它说成“类别”或者“类型”可能会更容易理解一些。比如“人”这种动物就是一个类,而具体某一个人就是“人”这个类的一个实例,“人”可以有许多实例(地球人超过六十亿了),但“人”这个类只有一个。你或许会说那男人和女人不也是人么?怎么只能有一个?其实这里要谈到一个继承的东西,后边才讲,请继续看下去。
4
如何建立一个类?
5
在C
++
中是以class来声明一个类的,JavaScript与C
++
不同,它使用了与函数一样的function来声明,这就让许多学Jscript的朋友把类与函数混在一起了,在Jscript中函数与类确实有些混,但使用久了自然而然会理解,这篇文章是针对想进攻面向对象编程的朋友而写,就不打算一下子讨论得太深了。
6
请看下边这个类的定义:
7
function
WuYouUser()
8
{
9
this
.Name;
//
名字
10
}
11
上边的代码定义了一个WuYouUser(无忧用户)类,它有个属性:Name(名字)。Name就是WuYouUser类的一个属性。
12
一个类有固定的属性,但类的实例却有不同的属性值,就像我是属于“人”这个类的,性别是男,而我有一个女同学,她也属于“人”类,但她的性别属性值却为女。
13
那么如何声明某个类的一个实例呢?非常简单:
14
var
Wo
=
new
WuYouUser();
//
实例一:“我”
15
var
Biyuan
=
new
WuYouUser();
//
实例二:“碧原”(Biyuan哥,不好意思。。。嘿嘿)
16
类的属性
17
这个Wo(我)就是WuYouUser类的一个实例,它拥有WuYouUser给它的一切:Name属性、Sex属性以及Age属性,我们可以这样子来设置它的属性:
18
Wo.Name
=
"
泣红亭
"
;
19
很简单是不是?试着运行
20
window.document.write(Wo.Name);
21
看看,是不是输出了我的名字:泣红亭?
22
同样设置一下碧原兄的属性
23
Biyuan.Name
=
"
碧原
"
;
24
运行
25
window.document.write(Biyuan.Name);
26
可以看到输出了
"
碧原
"
,也就说明了Biyuan与Wo同样是WuYouUser类的实例,但却是不同的实体,具有不同的属性值。
27
属性是可以设置默认值的,无忧里都有记录大家各自发了多少贴子,我们也同样给WuYouUser类添加一个发贴数量的属性ArticleCount
28
function
WuYouUser()
29
{
30
this
.Name;
//
名字
31
this
.ArticleCount
=
;
32
}
33
一个无忧新用户刚注册完之后他的发贴数量为0,在上边的代码中可以看到直接给属性ArticleCount设置值为0。
34
可以运行一下这样的代码:
35
var
Wo
=
new
WuYouUser();
36
window.document.write(Wo.ArticleCount);
37
可以看到输出了0,说明ArticleCount属性被我们成功设置默认值为0
38
类的方法
39
方法这个词不大好理解,我觉得说成行为会更容易理解。一个人具有许多共同的行为,比如睡觉、吃饭、走路等等,现在我们给WuYouUser类添加一个发贴的方法。
40
function
WuYouUser()
41
{
42
this
.Name;
43
this
.ArticleCount
=
;
44
this
.NewArticle
=
function
()
45
{
46
/*
47
*
48
* 具体如何发贴我们大家都知道,不就是打打字,加加图片再按一下保存之类的按钮么?
49
* 关于具体如何发贴的代码没有必要在这里写出来,我们要了解的仅仅是方法的定义与使用
50
* 我们在这里实现一个最简单的功能,也是很重要的功能:给我们的发贴数量加上1!
51
* 注意:恐龙等级就是这样加出来的,因此呀……大家狂发贴吧。。。
52
*/
53
this
.ArticleCount
++
;
54
}
55
}
56
既然定义好了这个方法,我们来试试效果如何:
57
var
Wo
=
new
WuYouUser();
58
Wo.NewArticle();
59
document.write(Wo.ArticleCount);
60
可以看到输出了1,说明我们发贴成功了!真是有历史纪念意义的一刻,离恐龙等级又近一步了。
61
静态属性
62
静态属性又称公共属性,它不属于某个类的实例,而是直接属于某个类。
63
比如说无忧用户有一个属性:注册用户的数量,它是属于整个无忧用户的,而不是属于泣红亭或者谁的
64
静态属性的声明方法是:
65
类名.prototype.属性名
=
属性值;
66
比如给WuYouUser类定义一个注册用户的数量Count:
67
WuYouUser.prototype.Count
=
;
68
那么如何读取它呢?有两种方法:
69
1
. 直接用 WuYouUser.prototype.Count
70
2
. 使用Wo.Count
71
这两者没有区别,都是得到0
72
虽然读取方法可以有两种,但在改变它的时候却得特别小心了,请看下边代码
73
var
Biyuan
=
new
WuYouUser();
74
WuYouUser.prototype.Count
++
;
75
document.write(Wo.Count);
76
document.write(Biyuan.Count);
77
你会发现两者的Count属性都是1,也就是说WuYouUser.prototype.Count改变了会影响到各个实例的相应属性,其实原理就是Wo、Biyuan的Count属性与WuYouUser.prototype.Count根本就是同一个!
78
现在来看另外一段代码:
79
var
Biyuan
=
new
WuYouUser();
80
Biyuan.Count
++
;
//
特别注意一下这里,这是直接改变Biyuan的Count属性
81
document.write(Biyuan.Count);
//
输出 1
82
document.write(WuYouUser.prototype.Count);
//
输出 0
83
document.write(Wo.Count);
//
同样输出0,为什么?
84
可以看到如果直接修改实例的静态属性值,那么会出现其它实例甚至类的静态属性与它不同步了?这是因为直接修改的时候,该实例会生成一个属于该实例的属性Count,这个时候Biyuan.Count不再与WuYouUser.prototype.Count是同一个了,也不与Wo.Count是同一个,这个Count属性是属于Biyuan自己所有的,以后改变了它也只是影响它自己而已。
85
因此如果不是特别的需要,建议不管在读取还是赋值的时候,都统一使用WuYouUser.prototype.Count这样的方式,以做到万无一失!
86
静态方法
87
与静态属性相似,它也有个另称:公共方法,同样属于类本身的。
88
静态方法的定义方式是:
89
类名.方法名
=
function
(参数1,参数2
参数n)
90
{
91
//
方法代码
92
}
93
我们现在就来定义一个无忧用户类的注册新用户静态方法:
94
WuYouUser.prototype.AddOne
=
function
()
95
{
96
//
*** 同样具体代码不写出来,给静态属性Count增加1,表示注册用户数量又多一个
97
WuYouUser.prototype.Count
++
;
98
}
99
现在我们来看看如何用它,同样有两种方法:
100
1
.直接使用WuYouUser.prototype.AddOne()
101
2
.使用某实例的AddOne()
102
103
这两种方法没有什么不同:
104
105
var
Wo
=
new
WuYouUser();
106
var
Biyuan
=
new
WuYouUser();
107
document.write(WuYouUser.prototype.Count);
//
108
109
Wo.AddOne();
110
document.write(WuYouUser.prototype.Count);
//
1
111
document.write(Wo.Count);
//
1
112
document.write(Biyuan.Count);
//
1
113
114
WuYouUser.prototype.AddOne();
115
document.write(WuYouUser.prototype.Count);
//
2
116
document.write(Wo.Count);
//
2
117
document.write(Biyuan.Count);
//
2
118
119
可以看出不管是使用Wo.AddOne()还是WuYouUser.prototype.AddOne()效果都是一样的,都是给WuYouUser.prototype.Count加上1
120
121
现在再看一段代码:
122
function
NewClass()
//
由于上边的WuYouUser类不合适当这个例子的代码,我声明了一个新类NewClass
123
{
124
this
.Name
=
"
泣红亭
"
;
//
这里默认值为我的名字
125
}
126
127
NewClass.prototype.ChangeName
=
function
(NewName)
128
{
129
this
.Name
=
NewName;
130
}
131
132
var
Wo
=
new
NewClass();
133
Wo.ChangeName(
"
郑运涛
"
);
//
我的真名
134
135
可以看到Wo.Name确实已经变成了
"
郑运涛
"
,这个方法似乎是可以用的,但里边是不是内有天机呢?
136
再看下边的代码,类的定义以及ChangeName的定义我们照样,但改变一下下边的代码:
137
138
NewClass.prototype.ChangeName(
"
郑运涛
"
);
139
document.write(NewClass.Name);
//
undefined,即未定义
140
document.write(NewClass.prototype.Name);
//
郑运涛
141
var
Wo
=
new
NewClass();
142
document.write(Wo.Name);
//
泣红亭
143
144
可以看到我们并没有定义NewClass.prototype.Name这个静态属性,但编译器给我们自己加了一个。
145
可是再看下边输出Wo.Name,它并不是为
"
郑运涛
"
,而是原来的默认值
"
泣红亭
"
,说明了什么?
146
其实很简单,看一下NewClass的定义里已经有Name这个属性,因此Wo也有自己的Name属性,它跟NewClass.prototype.Name并不是同一个的,因此就还是那样子。
147
148
那为什么前一个例子运行了Wo.ChangeName(
"
郑运涛
"
)却能够实现改变Wo.Name属性呢?其实在这里跟改变Wo.Count的值是同一个道理,编译器自动给Wo增加了一个方法ChangeName,这个方法代码与NewClass.prototype.ChangeName一样,但Wo.ChangeName是Wo这个实例所特有的,而非NewClass.prototype.ChangeName!
149
150
分析可知道在静态方法里尽量不要使用this这样的关键字来引用实例本身的属性,除非你有特别的目的,而且能够清楚地明白这里边的运行机制!
151
152
如果真的需要在静态方法里使用this,可以直接把this当作参数传进去:
153
154
NewClass.ChangeName
=
function
(This,NewName)
//
注意这里是This,不是this
155
{
156
This.Name
=
NewName;
157
}
158
构造函数
159
一个类在初始化的时候其实也是一个函数的执行过程,这个函数就是构造函数,我们看一下下边的代码:
160
161
function
WuYouUser()
162
{
163
this
.Name
=
"
泣红亭
"
;
//
默认定义为泣红亭
164
alert(
this
.Name);
165
}
166
var
Wo
=
new
WuYouUser();
//
可以看到出现一个窗口显示泣红亭三个字
167
168
可以看出类的定义不仅仅是定义了它的属性与方法,还同时可以加入一些代码,而这些代码就是该类的构造函数的代码,在实例声明过程中被执行!
169
其实说起来,类的属性与类的方法都是在构造函数里执行定义的,看下边的代码:
170
171
function
WuYouUser()
172
{
173
this
.Name
=
"
泣红亭
"
;
174
return
;
175
this
.Sex
=
"
男
"
;
176
}
177
var
Wo
=
new
WuYouUser();
178
document.write(Wo.Name);
//
泣红亭
179
document.write(Wo.Sex);
//
undefined,即未定义
180
181
看得出什么?Sex属性是在return;之后的,而WuYouUser类的构造函数遇到return即停止运行,换句话说this.Sex
=
"
男
"
;这一行是没有被执行,即Sex属性根本没有被定义!
182
183
构造函数可以有参数,参数值在声明实例的时候被传入:
184
function
WuYouUser(Name)
185
{
186
this
.Name
=
Name;
187
}
188
var
Wo
=
new
WuYouUser(
"
泣红亭
"
);
189
document.write(Wo.Name);
//
泣红亭
190
191
构造函数不需要返回值,但如果你设置了返回值,可以把它当成一个函数来使用。
192
function
Sum(a, b)
193
{
194
this
.a
=
a;
195
this
.b
=
b;
196
return
this
.a
+
this
.b;
197
}
198
document.write(Sum(
12
,
23
));
//
输出的是12与23的和35
199
var
Obj
=
new
Sum(
12
,
23
);
200
document.write(Obj.a)
//
12
201
document.write(Obj.b)
//
23
202
203
感觉挺奇妙,对吧?我写这文章写着写着也觉得挺奇妙的,呵呵!
204
205
但强烈建议不要把一个类当成一个函数来使用!如果你需要的是一个函数,请直接写成函数而不要写成类,以免搞混了。
206
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::继承:::::::::::::::::::::::::::::::::::::::::::::::::::
207
继承这个词在面向对象的编程里是非常重要的,虽然JavaScript并不是真正面向对象的语言,而是跟VB一样是基于对象的语言,它同样提供了继承机制。
208
文章开头时谈到了男人与女人,这也同样是两个不同的类,但却具有相同的一些属性以及方法,而这些相同的特性是来自“人”这个类的,换句话说男人与女人继承了“人”的所有特性!但是男人与女人却有其不同的地方,编程语言里的继承也一样,一个类A继承了另一个类B,那么类B就是类A的父类,类A就是类B的派生类,也称为子类。比如男人就是人的派生类,而人就是男人的父类。最高一级的类称为基类,想象一下就可以明白,男人继承自人,男孩继承自男人,人就是男孩的基类,男人就是男孩的父类。
209
>>>>>>>>>>>>>>>>>>>>
210
题外:多重继承
211
212
这里再涉及一个多重继承的话题,但如果你仅仅是学JavaScript的话就没有必要看下去,因为JavaScript不提供多重继承,准确一点说没有一种简单而标准的方法来实现多重继承(其实是有办法实现的,只不过麻烦了一点,而且确实没有必要)。
213
214
在C
++
中是有多重继承的概念的,这里是讨论JavaScript,因此不打算讲,只是说说它的一点点思想以供参考。
215
216
在上边男孩的继承问题中,男孩其实不仅仅是继承自男人,还继承自孩子(有男孩子,也有女孩子)这个类,因此,它同时继承了两个类:男人与男孩,这就是所谓的多重继承。
217
218
好,这个问题打住,我们还是回归主题。
219
>>>>>>>>>>>>>>>>>>>>
220
221
先看第一个类的定义:
222
223
function
A()
224
{
225
this
.Name
=
"
泣红亭
"
;
226
alert(
this
.Name);
227
}
228
229
这个类定义了一个属性Name,默认值为
"
泣红亭
"
230
231
现在看第二个类的定义:
232
233
function
B()
234
{
235
this
.Sex
=
"
男
"
;
236
alert(
this
.Sex);
237
}
238
239
定义了一个属性Sex,默认值为
"
男
"
240
241
继承的方式就是 子类.prototype
=
new
父类();
242
现在我们来让B类继承A类:
243
244
B.prototype
=
new
A();
245
246
247
248
运行这一段代码:
249
250
var
Obj
=
new
B();
//
首先打开警告窗口显示"泣红亭",再显示"男"
251
252
可以从上边的结果看出B类继承了A类,拥有了A类的属性Name,并且执行了A类的构造函数,而且A类的构造函数在B类的构造函数执行之前执行。因此我们利用这个可以实现重写父类的方法以及重设置父类某属性的默认值:
253
254
function
A()
255
{
256
this
.Name
=
"
泣红亭
"
;
257
this
.Show
=
function
()
258
{
259
alert(
"
这是A类的Show方法
"
);
260
}
261
alert(
this
.Name);
262
}
263
264
function
B()
265
{
266
this
.Name
=
"
郑运涛
"
;
267
this
.Show
=
function
()
268
{
269
alert(
"
这是B类的Show方法
"
);
270
}
271
alert(
this
.Name);
272
}
273
274
var
Obj
=
new
B();
275
Obj.Show();
276
277
结果出现了三次警告窗口,第一个内容为泣红亭,是执行A类的构造函数里的alert(
this
.Name),那时候Name属性值还为
"
泣红亭
"
,因为B类的构造函数还没执行,第二次内容为
"
郑运涛
"
,这是B类里的alert(
this
.Name),因为B类的构造函数里给Name重赋值为
"
郑运涛
"
。最后是调用了Obj.Show(),执行了不是A类的Show方法里的Show(显示
"
这是A类的Show方法
"
),而是执行了B类的Show(显示
"
这是B类的Show方法
"
),很明显Show方法被重写了。
278
279
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
280
类作为一个对象时的属性与方法(不知道如何简洁地表达,因此用了这么长的题目)
281
282
不知道在这里谈这个话题是否有点混人耳目,但又觉得不谈这篇文章就不算完整,因为文章目的就是要让人搞清楚类的方方面面。
283
284
看了这一小节的题目,或许你会觉得奇怪,类就是类,怎么会“作为一个对象”呢?在JavaScript里,一切都是对象,包括类!对象可以有属性,可以有方法,类也同样可以有,但这个非常容易跟前边说到的静态属性与静态方法搞混了,因此要仔细看清楚两者的分别!
285
286
定义一个类:
287
function
WuYouUser()
288
{
289
this
.Name
=
"
泣红亭
"
;
290
}
291
292
定义类作为一个对象时的属性:
293
294
WuYouUser.Url
=
"
http://www.51js.com
"
;
//
静态属性的定义是:WuYouUser.prototype.Url = "http://www.51js.com";
295
var
Wo
=
new
WuYouUser();
296
document.write(WuYouUser.Url);
//
http://www.51js.com
297
document.write(Wo.Url);
//
undefined,即未定义!注意这里的未定义
298
299
从这里可以看出Url这个属性是WuYouUser自个所有,改变了它与其它类以及它的子类完全无关!
300
301
引用类的属性只有一个办法,就是类名.属性名,改变它也一样。
302
303
定义类作为一个对象时的方法:
304
305
WuYouUser.ChangeUrl
=
function
()
306
{
307
this
.Url
=
"
http://51js.com
"
;
308
}
309
310
你或许会觉得奇怪,这里的this是什么?因为ChangeUrl这个方法是属于对象WuYouUser的,因此this指的就是WuYouUser本身!
311
312
可以运行下边的代码试试:
313
314
document.write(WuYouUser.Url);
315
WuYouUser.ChangeUrl();
316
document.write(WuYouUser.Url);
317
明显ChangeUrl直接修改了WuYouUser.Url的值,因此后边才能输出http:
//
51js.com
318
319