前些天写了一篇关于JavaScript基础的帖子"JavaScript基础之对象"。在那篇帖子中说马上就会写一些关于继承方面的东西。可是上周杂事太多,都没有时间动笔,就一直拖到今天才终于下笔写了。上一篇帖子中很多人认为我自己的东西太少了,所以这篇帖子中在说完基础知识之后我会给出一个完整的例子。 大家都知道面向对象语言的三大特性为:封装、继承、多态。面向对象的语言之所以强大就全依赖于这三大特性。脚本作为一种准面向对象的语言虽然有先天的不足,但还是能在某种程度上去实现这些特性。实现最多、技术最成熟的就数继承了,不过脚本也不能像C#一样简单地用一个":"就能实现继承,而是要自己去实现继承的细节。
1 继承的几种实现方法
要实现继承首先就必须要有子类、父类(超类、基类等叫法)。子类去继承父类的方法和属性。在脚本里所有开发人员定义的类都可以作为父类,不过本地类(native object)和宿主类(host object)不能作为父类。由于脚本里面没有访问修饰符,父类所有的属性和方法都可以被子类直接访问。子类也可以扩展父类的属性和方法。
1.1 call()方法
Call方法是脚本的一个内置(build in)方法。官方说明为:调用一个对象的方法,以另一个对象替换当前对象。语法为:call([thisObj[,arg1[, arg2[, [,.argN]]]]])。用这个方法可以很容易地模拟继承的实现:
1
function funCreateElement(sClassName)
2
{
3
this.sAClassName=sClassName;
4
5
this.createALink=function()
6
{
7
var oA=document.createElement("a");
8
oA.className=this.sAClassName;
9
return oA;
10
}
11
}
类(funCreateElement)有一个属性(sAClassName)和一个方法(createALink)。它的作用是创建一个DOM元素。这个类使用this关键字给属性赋值,这样就确保了所有的实例指向的都是自己的值。
1
function funCurrNavi()
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的这个特性,我们就可以实现继承方法了。如果用原型链的方式重定义前面的例子,它们将变为下面的形式:
1
function funCreateElement()
2
{
3
}
4
5
funCreateElement.prototype.sAClassName=sClassName; // 样式类
6
funCreateElement.prototype.createALink=function()
7
{
8
var oA=document.createElement("a");
9
oA.className=this.sAClassName;
10
return oA;
11
}
12
13
14
function funCurrNavi()
15
{
16
}
17
18
funCurrNavi.prototype=new funCreateElement();
19
funCurrNavi.prototype.showMeInDiv=function()
20
{
21
}
这种方式采用funCurrNavi.prototype=new funCreateElement()方法,将funCreateElement类里面所有的属性和方法都复制到funCurrNavi类里面。非常的快捷。这种方式虽然非常的快捷,但是也有其缺点。采用这种方式时子类所有的属性和方法都必须在funCurrNavi.prototype=new funCreateElement()语句之后。因为该语句会覆盖funCurrNavi类原有的prototype类,添加了新的属性和方法的原有prototype类将被销毁。而且父类和子类都没有参数,只能通过设置属性的方法传入参数。
1.4 混合方式
混合方式就是指将前两种方式结合起来使用,取长避短。在call方式里面定义类的方式不理想,相同的方法在实例里面被重复的实例化了。原型链的方法则因为没有参数而不理想,使用参数前得额外的去设置其属性,不如构造函数的参数来的方便。根据上一篇帖子中定义类的混合方式,我们重新定义funCreateElement类:
1
function funCreateElement(sClassName)
2
{
3
this.sAClassName=sClassName;
4
}
5
6
funCreateElement.prototype.createALink=function()
7
{
8
var oA=document.createElement("a");
9
oA.className=this.sAClassName;
10
return oA;
11
}
然后在funCurrNavi类里面采用call方式继承funCreateElement类的属性,采用原型链的方式继承其方法。如下:
1
function funCurrNavi(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
var oLink=this.createALink();
13
oLink.text=this.LinkText;
14
}
采用funCreateElement.call(this,"tblNavigatorA")方式继承其参数,funCurrNavi.prototype=new funCreateElement()方式继承其方法。从而达到方法不会被实例化多次的目的。
2 实例
前面讲述了几种继承的实现方式,这一小节就利用前面讲述的方法去练习一个小例子。这个例子是根据一个字符串去呈现一个树状的列表,这种需求常见于页面的导航中。由于只是为了辅助讲述继承,例子有些地方不免有些牵强附会和小题大做的地方。这些就希望各位网友包涵了,请各位珍惜自己的砖头了。我在这里戴上钢盔等候大家的批评了。:)
下面是其运行成功的样图:


左面的那个是用table作为容器,右边的那个是用div作为容器。
1
<html xmlns="http://www.w3.org/1999/xhtml">
2
<head>
3
<title>Create Admin Pages' Navigator</title>
4
<style type="text/css">
5
/**//*导航table的样式*/.tblNavigator
6
{
7
border: 2px solid #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: 1px solid #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
<div id="divMain">
42
</div>
43
<div id="divMain2"
class="tblNavigator">
44
</div>
45
46
<script type="text/javascript">
47
48
var sNavigatorMain =
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
function funCreateElement(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
var oA=document.createElement("a");
95
oA.href=sUrl; // 链接文本
96
oA.className=this.sAClassName; // 样式类
97
oA.target=sTarget; // 打开链接的目标
98
oA.appendChild(document.createTextNode(sText)); // 显示文本
99
100
return oA;
101
}
102
103
/**//*
104
* 创建Div元素
105
* sText:显示的文本
106
* 返回:Div元素
107
*/
108
funCreateElement.prototype.createDivBlank=function()
109
{
110
var oDiv=document.createElement("div");
111
oDiv.className=this.sAClassName;
112
return oDiv;
113
}
114
115
/**//*
116
* 创建Div元素
117
* sText:显示的文本
118
* 返回:Div元素
119
*/
120
funCreateElement.prototype.createDivNoLink=function(sText)
121
{
122
var oDiv=document.createElement("div");
123
oDiv.className=this.sAClassName;
124
oDiv.appendChild(document.createTextNode(sText));
125
126
return oDiv;
127
}
128
129
/**//*
130
* 创建一个模块的节点
131
* oNaviJSON:一个模块节点的文本
132
* oParentObject:父节点
133
* sClassNameRoot:根节点的样式类
134
* sClassNameNode:子节点的样式类
135
*/
136
function funCurrNavi(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;
1 继承的几种实现方法
要实现继承首先就必须要有子类、父类(超类、基类等叫法)。子类去继承父类的方法和属性。在脚本里所有开发人员定义的类都可以作为父类,不过本地类(native object)和宿主类(host object)不能作为父类。由于脚本里面没有访问修饰符,父类所有的属性和方法都可以被子类直接访问。子类也可以扩展父类的属性和方法。
1.1 call()方法
Call方法是脚本的一个内置(build in)方法。官方说明为:调用一个对象的方法,以另一个对象替换当前对象。语法为:call([thisObj[,arg1[, arg2[, [,.argN]]]]])。用这个方法可以很容易地模拟继承的实现:
1
function funCreateElement(sClassName)2

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

{7
var oA=document.createElement("a");8
oA.className=this.sAClassName;9
return oA;10
}11
}类(funCreateElement)有一个属性(sAClassName)和一个方法(createALink)。它的作用是创建一个DOM元素。这个类使用this关键字给属性赋值,这样就确保了所有的实例指向的都是自己的值。
1
function funCurrNavi()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的这个特性,我们就可以实现继承方法了。如果用原型链的方式重定义前面的例子,它们将变为下面的形式:
1
function funCreateElement()2

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

{8
var oA=document.createElement("a");9
oA.className=this.sAClassName;10
return oA;11
} 12
13
14
function funCurrNavi()15

{ 16
}17
18
funCurrNavi.prototype=new funCreateElement();19
funCurrNavi.prototype.showMeInDiv=function()20

{21
}这种方式采用funCurrNavi.prototype=new funCreateElement()方法,将funCreateElement类里面所有的属性和方法都复制到funCurrNavi类里面。非常的快捷。这种方式虽然非常的快捷,但是也有其缺点。采用这种方式时子类所有的属性和方法都必须在funCurrNavi.prototype=new funCreateElement()语句之后。因为该语句会覆盖funCurrNavi类原有的prototype类,添加了新的属性和方法的原有prototype类将被销毁。而且父类和子类都没有参数,只能通过设置属性的方法传入参数。
1.4 混合方式
混合方式就是指将前两种方式结合起来使用,取长避短。在call方式里面定义类的方式不理想,相同的方法在实例里面被重复的实例化了。原型链的方法则因为没有参数而不理想,使用参数前得额外的去设置其属性,不如构造函数的参数来的方便。根据上一篇帖子中定义类的混合方式,我们重新定义funCreateElement类:
1
function funCreateElement(sClassName)2

{3
this.sAClassName=sClassName;4
}5
6
funCreateElement.prototype.createALink=function()7

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


左面的那个是用table作为容器,右边的那个是用div作为容器。
1
<html xmlns="http://www.w3.org/1999/xhtml">2
<head>3
<title>Create Admin Pages' Navigator</title>4

<style type="text/css">
5

/**//*导航table的样式*/.tblNavigator6

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

/**//*每个根节点的样式*/.tblNavigatorRoot16

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

/**//*根节点的子节点的样式*/.tblNavigatorTD23

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

/**//*导航中的超链接*/.tblNavigatorA32

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

46
<script type="text/javascript">47
48
var sNavigatorMain = 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
function funCreateElement(sClassName)80

{81
// 样式类82

this.sAClassName=sClassName;
83
}84
85

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

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

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

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

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

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

/**//* 130
* 创建一个模块的节点131
* oNaviJSON:一个模块节点的文本132
* oParentObject:父节点133
* sClassNameRoot:根节点的样式类134
* sClassNameNode:子节点的样式类135
*/136
function funCurrNavi(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;
