JavaScript基础之继承(附实例)
前些天写了一篇关于JavaScript基础的帖子"JavaScript基础之对象"。在那篇帖子中说马上就会写一些关于继承方面的东西。可是上周杂事太多,都没有时间动笔,就一直拖到今天才终于下笔写了。上一篇帖子中很多人认为我自己的东西太少了,所以这篇帖子中在说完基础知识之后我会给出一个完整的例子。
大家都知道面向对象语言的三大特性为:封装、继承、多态。面向对象的语言之所以强大就全依赖于这三大特性。脚本作为一种准面向对象的语言虽然有先天的不足,但还是能在某种程度上去实现这些特性。实现最多、技术最成熟的就数继承了,不过脚本也不能像C#一样简单地用一个":"就能实现继承,而是要自己去实现继承的细节。
1 继承的几种实现方法
要实现继承首先就必须要有子类、父类(超类、基类等叫法)。子类去继承父类的方法和属性。在脚本里所有开发人员定义的类都可以作为父类,不过本地类(native object)和宿主类(host object)不能作为父类。由于脚本里面没有访问修饰符,父类所有的属性和方法都可以被子类直接访问。子类也可以扩展父类的属性和方法。
1.1 call()方法
Call方法是脚本的一个内置(build in)方法。官方说明为:调用一个对象的方法,以另一个对象替换当前对象。语法为:call([thisObj[,arg1[, arg2[, [,.argN]]]]])。用这个方法可以很容易地模拟继承的实现:
类(funCreateElement)有一个属性(sAClassName)和一个方法(createALink)。它的作用是创建一个DOM元素。这个类使用this关键字给属性赋值,这样就确保了所有的实例指向的都是自己的值。
1
functionfunCreateElement(sClassName)
2

{
3
this.sAClassName=sClassName;
4
5
this.createALink=function()
6

{
7
varoA=document.createElement("a");
8
oA.className=this.sAClassName;
9
returnoA;
10
}
11
}
1
functionfunCurrNavi()
2

{
3
//继承元素创建类
4
funCreateElement.call(this,"tblNavigatorA");
5
}
这样就实现了一个最简单的继承了。在funCurrNavi的实例里就可以直接使用funCreateElement类里面的方法了。
var o=new funCurrNavi();
o.createALink();
1.2 apply()方法
apply方法也是脚本的一个内置(build in)方法。其用法和call对象类似。官方说明为:应用某一对象的一个方法,用另一个对象替换当前对象。语法为:apply([thisObj[,argArray]])。实际上这两个的作用几乎是相同的,要注意的地方是call(thisObj[,arg1[, arg2[,)中的arg参数可以是变量,而apply([thisObj[,argArray]])中的参数为数组集合。由于和call方法就只有参数的不同而已所以就不举例子了。
1.3 原型链
Prototype对象是所有类的源头。如果修改了prototype类的方法则这个修改会反映到所有类的实例里面。比如:在string.prototype里面加一个方法trim(),String.prototype.trim=function(){}。那么所有的字符串都会具有trim()方法了("aa".trim())。由于prototype的这个特性,我们就可以实现继承方法了。如果用原型链的方式重定义前面的例子,它们将变为下面的形式:
这种方式采用funCurrNavi.prototype=new funCreateElement()方法,将funCreateElement类里面所有的属性和方法都复制到funCurrNavi类里面。非常的快捷。这种方式虽然非常的快捷,但是也有其缺点。采用这种方式时子类所有的属性和方法都必须在funCurrNavi.prototype=new funCreateElement()语句之后。因为该语句会覆盖funCurrNavi类原有的prototype类,添加了新的属性和方法的原有prototype类将被销毁。而且父类和子类都没有参数,只能通过设置属性的方法传入参数。
1
functionfunCreateElement()
2

{
3
}
4

5
funCreateElement.prototype.sAClassName
=
sClassName;
//
样式类
6
funCreateElement.prototype.createALink
=
function()
7

{
8
varoA=document.createElement("a");
9
oA.className=this.sAClassName;
10
returnoA;
11
}
12

13

14
functionfunCurrNavi()
15

{
16
}
17

18
funCurrNavi.prototype
=
new
funCreateElement();
19
funCurrNavi.prototype.showMeInDiv
=
function()
20

{
21
}
1.4 混合方式
混合方式就是指将前两种方式结合起来使用,取长避短。在call方式里面定义类的方式不理想,相同的方法在实例里面被重复的实例化了。原型链的方法则因为没有参数而不理想,使用参数前得额外的去设置其属性,不如构造函数的参数来的方便。根据上一篇帖子中定义类的混合方式,我们重新定义funCreateElement类:
1
functionfunCreateElement(sClassName)
2

{
3
this.sAClassName=sClassName;
4
}
5

6
funCreateElement.prototype.createALink
=
function()
7

{
8
varoA=document.createElement("a");
9
oA.className=this.sAClassName;
10
returnoA;
11
}
然后在funCurrNavi类里面采用call方式继承funCreateElement类的属性,采用原型链的方式继承其方法。如下:
1
functionfunCurrNavi(sLinkText)
2

{
3
funCreateElement.call(this,"tblNavigatorA");
4
5
this.LinkText=sLinkText;
6
}
7

8
funCurrNavi.prototype
=
new
funCreateElement();
//
继承超链接类
9
10
funCurrNavi.prototype.showMeInDiv
=
function()
11

{
12
varoLink=this.createALink();
13
oLink.text=this.LinkText;
14
}
采用funCreateElement.call(this,"tblNavigatorA")方式继承其参数,funCurrNavi.prototype=new funCreateElement()方式继承其方法。从而达到方法不会被实例化多次的目的。
2 实例
前面讲述了几种继承的实现方式,这一小节就利用前面讲述的方法去练习一个小例子。这个例子是根据一个字符串去呈现一个树状的列表,这种需求常见于页面的导航中。由于只是为了辅助讲述继承,例子有些地方不免有些牵强附会和小题大做的地方。这些就希望各位网友包涵了,请各位珍惜自己的砖头了。我在这里戴上钢盔等候大家的批评了。:)
下面是其运行成功的样图:


左面的那个是用table作为容器,右边的那个是用div作为容器。
1
<
htmlxmlns
=
"
http://www.w3.org/1999/xhtml
"
>
2
<
head
>
3
<
title
>
CreateAdminPages
'
Navigator</title>
4
<
styletype
=
"
text/css
"
>
5

/**/
/*导航table的样式*/
.tblNavigator
6

{
7
border:2pxsolid#FFD7AC;
8
width:200px;
9
margin:3px;
10
background:#fff;
11
margin-top:5px;
12
margin-bottom:5px;
13
border-collapse:collapse;
14
}
15

/**/
/*每个根节点的样式*/
.tblNavigatorRoot
16

{
17
height:30px;
18
line-height:30px;
19
text-align:left;
20
vertical-align:middle;
21
}
22

/**/
/*根节点的子节点的样式*/
.tblNavigatorTD
23

{
24
border-collapse:collapse;
25
border:1pxsolid#FFE6CB;
26
height:22px;
27
text-indent:15px;
28
background:#fff;
29
width:100%;
30
}
31

/**/
/*导航中的超链接*/
.tblNavigatorA
32

{
33
font-weight:700;
34
text-decoration:none;
35
color:Black;
36
width:100px;
37
}
38
</
style
>
39
</
head
>
40
<
body
>
41
<
divid
=
"
divMain
"
>
42
</
div
>
43
<
divid
=
"
divMain2
"
class
=
"
tblNavigator
"
>
44
</
div
>
45

46
<
scripttype
=
"
text/javascript
"
>
47

48
varsNavigatorMain
=
49

{
50
sNavigators:[
51

{
52
root:[
{name:'root1'}],
53
sons:[
{name:'root1Son1',url:'bcc'},
{name:'root1Son2',url:'bcc'}]
54
},
55

{
56
root:[
{name:'root2'}],
57
sons:[
{name:'root2Son1',url:'bcc'}]
58
},
59

{
60
root:[
{name:'root3'}],
61
sons:[
{name:'root3Son1',url:'bcc'},
{name:'root3Son2',url:'bcc'},
{name:'root3Son3',url:'bcc'}]
62
},
63

{
64
root:[
{name:'root4'}],
65
sons:[
{name:'root4Son1',url:'bcc'},
{name:'root4Son3',url:'bcc'}]
66
}
67
,
68

{
69
root:[
{name:'root4'}],
70
sons:[
{name:'root4Son1',url:'bcc'},
{name:'root4Son3',url:'bcc'}]
71
}
72
]
73
}
;
74

75

/**/
/*
76
*创建一个元素
77
*sClassName:元素的样式控制类
78
*/
79
functionfunCreateElement(sClassName)
80

{
81
//样式类
82
this.sAClassName=sClassName;
83
}
84

85

/**/
/*
86
*创建超链接元素
87
*sText:超链接显示的文本
88
*sUrl:超链接的href
89
*sTarget:打开链接的目标
90
*返回:超链接元素
91
*/
92
funCreateElement.prototype.createALink
=
function(sText,sUrl,sTarget)
93

{
94
varoA=document.createElement("a");
95
oA.href=sUrl;//链接文本
96
oA.className=this.sAClassName;//样式类
97
oA.target=sTarget;//打开链接的目标
98
oA.appendChild(document.createTextNode(sText));//显示文本
99
100
returnoA;
101
}
102

103

/**/
/*
104
*创建Div元素
105
*sText:显示的文本
106
*返回:Div元素
107
*/
108
funCreateElement.prototype.createDivBlank
=
function()
109

{
110
varoDiv=document.createElement("div");
111
oDiv.className=this.sAClassName;
112
returnoDiv;
113
}
114

115

/**/
/*
116
*创建Div元素
117
*sText:显示的文本
118
*返回:Div元素
119
*/
120
funCreateElement.prototype.createDivNoLink
=
function(sText)
121

{
122
varoDiv=document.createElement("div");
123
oDiv.className=this.sAClassName;
124
oDiv.appendChild(document.createTextNode(sText));
125
126
returnoDiv;
127
}
128

129

/**/
/*
130
*创建一个模块的节点
131
*oNaviJSON:一个模块节点的文本
132
*oParentObject:父节点
133
*sClassNameRoot:根节点的样式类
134
*sClassNameNode:子节点的样式类
135
*/
136
functionfunCurrNavi(oNaviJSON,oParentObject,sClassNameRoot,sClassNameNode)
137

{
138
//继承元素创建类
139
funCreateElement.call(this,"tblNavigatorA");
140
141
//当前导航的菜单
142
this.oNavi=oNaviJSON;
143
//当前导航的父节点
144
this.oParent=oParentObject;
145
//根节点的样式类
146
this.oNClassNameRoot=sClassNameRoot;
147
//子节点的样式类
148
this.oNClassNameNode=sClassNameNode;
149
}
150

151
//
继承超链接类
152
funCurrNavi.prototype
=
new
funCreateElement();
153

154
//
显示自己(在Table容器里)
155
funCurrNavi.prototype.showMeInTable
=
function()
156

{
157
//在父容器中添加一行,并将父节点写入
158
this.oParent.insertRow(0);
159
this.oParent.rows[0].onclick=function()
{alert(this.innerText)};
160
this.oParent.rows[0].insertCell(0);
161
this.oParent.rows[0].cells[0].className=this.oNClassName;
162
this.oParent.rows[0].cells[0].appendChild(document.createTextNode(this.oNavi.root[0].name));
163
164
varj=this.oNavi.sons.length;
165
for(vari=0;i<j;i++)
166

{
167
this.oParent.insertRow(i+1);
168
this.oParent.rows[i+1].insertCell(0);
169
this.oParent.rows[i+1].cells[0].className=this.oNClassNameNode;
170
this.oParent.rows[i+1].cells[0].appendChild(this.createALink(this.oNavi.sons[i].name,this.oNavi.sons[i].url,"_blank"));
171
}
172
}
173

174
//
显示自己(在Div容器里)返回:div
175
funCurrNavi.prototype.showMeInDiv
=
function()
176

{
177
//创建一个暂时的容器
178
varoDivContainer=document.createElement("div");
179
180
//在父容器中将父节点写入
181
varoDivRoot=this.createDivNoLink(this.oNavi.root[0].name);
182
oDivRoot.className=this.oNClassNameRoot;
183
oDivRoot.onclick=function()
{alert(this.innerText);};
184
oDivContainer.appendChild(oDivRoot);
185
186
//链接节点
187
varoDivLinkNode;
188
189
varj=this.oNavi.sons.length;
190
for(vari=0;i<j;i++)
191

{
192
oDivLinkNode=this.createDivBlank();
193
oDivLinkNode.appendChild(this.createALink(this.oNavi.sons[i].name,this.oNavi.sons[i].url,"_blank"));
194
oDivLinkNode.className=this.oNClassNameNode;
195
oDivContainer.appendChild(oDivLinkNode);
196
}
197
198
returnoDivContainer;
199
}
200

201

202

/**/
/*
203
*创建所有的节点
204
*oNavisJSON:所有节点的文本
205
*/
206
functionfunCurrNavis(oNavisJSON,sClassName)
207

{
208
//当前导航的菜单
209
this.oNavis=oNavisJSON;
210
//样式类
211
this.oNsClassName=sClassName;
212
}
213

214
//
创建所有的节点(以Table为容器)
215
funCurrNavis.prototype.createNavigatorsInTable
=
function()
216

{
217
//创建父Table
218
varoTable=document.createElement("table");
219
oTable.className=this.oNsClassName;
220
221
//创建tbody
222
varoTBody=document.createElement("tbody");
223
oTable.appendChild(oTBody);
224
225
varj=this.oNavis.sNavigators.length;
226
varCNavi;
227
228
for(vari=j-1;i>=0;i--)
229

{
230
CNavi=newfunCurrNavi(this.oNavis.sNavigators[i],oTBody,"tblNavigatorRoot","tblNavigatorTD");
231
CNavi.showMeInTable();
232
}
233
234
returnoTable;
235
}
236

237
//
创建所有的节点(以Div为容器)
238
funCurrNavis.prototype.createNavigatorsInDiv
=
function()
239

{
240
//创建容器
241
varoContainer=document.createDocumentFragment();
242
243
varj=this.oNavis.sNavigators.length;
244
varCNavi;
245
246
for(vari=0;i<j;i++)
247

{
248
CNavi=newfunCurrNavi(this.oNavis.sNavigators[i],
{},"tblNavigatorRoot","tblNavigatorTD");
249
oContainer.appendChild(CNavi.showMeInDiv());
250
}
251
252
returnoContainer;
253
}
254

255

/**/
/*
256
*供调用的函数
257
*sNavigatorMain:所有节点的文本
258
*sParentID:导航父容器的ID
259
*/
260
functionshowNavigator(sNavigatorMain,sParentID,sFlag)
261

{
262
varoTemp=eval("ddkk="+sNavigatorMain);
263
varmain=newfunCurrNavis(oTemp,"tblNavigator");
264
265
if(sFlag=="div")
266

{
267
document.getElementById(sParentID).appendChild(main.createNavigatorsInDiv());
268
}
269
else
270

{
271
document.getElementById(sParentID).appendChild(main.createNavigatorsInTable());
272
}
273
}
274

275
</
script
>
276

277
<
scripttype
=
"
text/javascript
"
>
278
//
showNavigator("{sNavigators:[{root:[{name:'用户管理'}],sons:[{name:'个人信息管理',url:'~/UserMng/frmMyInfoMng.aspx'},{name:'本单位用户管理',url:'~/UserMng/frmUserMng.aspx'},{name:'本单位信息管理',url:'~/SysMaintenance/frmDeptInfo.aspx'}]},{root:[{name:'申报管理'}],sons:[{name:'项目管理',url:'~/Declaration/frmProjAppMng.aspx'}]},{root:[{name:'留言板管理'}],sons:[{name:'给管理员留言',url:'~/Information/frmQuestion.aspx'}]}]}","divMain","table");
279

280
//
showNavigator("{sNavigators:[{root:[{name:'用户管理'}],sons:[{name:'个人信息管理',url:'~/UserMng/frmMyInfoMng.aspx'},{name:'本单位用户管理',url:'~/UserMng/frmUserMng.aspx'},{name:'本单位信息管理',url:'~/SysMaintenance/frmDeptInfo.aspx'}]},{root:[{name:'申报管理'}],sons:[{name:'项目管理',url:'~/Declaration/frmProjAppMng.aspx'}]},{root:[{name:'留言板管理'}],sons:[{name:'给管理员留言',url:'~/Information/frmQuestion.aspx'}]}]}","divMain2","div");
281
</
script
>
282

283
</
body
>
284
</
html
>
在这个实例里面采用了JSON字符串作为数据源"{sNavigators:[{root:[{name:'用户管理'}],sons:[{name:'个人信息管理',url:'~/UserMng/frmMyInfoMng.aspx'},{name:'本单位用户管理',url:'~/UserMng/frmUserMng.aspx'},{name:'本单位信息管理',url:'~/SysMaintenance/frmDeptInfo.aspx'}]},{root:[{name:'申报管理'}],sons:[{name:'项目管理',url:'~/Declaration/frmProjAppMng.aspx'}]},{root:[{name:'留言板管理'}],sons:[{name:'给管理员留言',url:'~/Information/frmQuestion.aspx'}]}]}"。其格式好了的形式类似于上面例子中的
1
varsNavigatorMain
=
2

{
3
sNavigators:[
4

{
5
root:[
{name:'root1'}],
6
sons:[
{name:'root1Son1',url:'bcc'},
{name:'root1Son2',url:'bcc'}]
7
},
8

{
9
root:[
{name:'root2'}],
10
sons:[
{name:'root2Son1',url:'bcc'}]
11
},
12

{
13
root:[
{name:'root3'}],
14
sons:[
{name:'root3Son1',url:'bcc'},
{name:'root3Son2',url:'bcc'},
{name:'root3Son3',url:'bcc'}]
15
},
16

{
17
root:[
{name:'root4'}],
18
sons:[
{name:'root4Son1',url:'bcc'},
{name:'root4Son3',url:'bcc'}]
19
}
20
,
21

{
22
root:[
{name:'root4'}],
23
sons:[
{name:'root4Son1',url:'bcc'},
{name:'root4Son3',url:'bcc'}]
24
}
25
]
26
}
;
用JSON字符串作为数据源非常的方便,他能直接转化成类。上面的例子非常简单就不详细地说明了,通过注释就能看明白了。