Jscript中内置了一些类,例如String、Array、Math等,用户可以直接从这些类实例化出对象,并使用其中的属性和方法。单凭这一点,不能说js符合面向对象语言的特征。一种面向对象的语言,应当具有封装,继承,多态等基本特性。Jscript并没有直接提供实现这些特性的方法,但是,也并非完全无法实现。
一、Js中的封装
一种面向对象的语言应当允许用户创建自定义类型,这一点js做到了,只是它的自定义类型不是用class限定,而是作为function,这个function的作用相当于其他语言中的构造函数。在自定义类型中,用户可以添加属性、方法。但是,js中并未显式提供public、private、protected等访问限定,也没有提供static之类的作用域限定。下面通过几个例子,逐一说明创建自定义类型及实现各种限定的方法。
示例1-1:该例子演示了创建及使用包含属性和方法的自定义类型的过程。本例子中,我们创建了一个名为MyClass的类,其中包含了一个Name属性和一个showName方法,然后将这个类实例化,即可调用其中的方法,使用其中的属性。注意,在类中定义的方法和属性应当使用this关键字绑定到这个类,调用时使用绑定名字来访问,而不是通过实际名字。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function MyClass(name)
6
{
7
this.Name = name;
8
this.showName = writeName;
9
10
function writeName()
11
{
12
document.write(this.Name);
13
document.write("<br />");
14
}
15
}
16
-->
17
</script>
18
</head>
19
<body>
20
<script language="Jscript">
21
<!--
22
var myClass = new MyClass("Tom");
23
myClass.showName();
24
document.write(myClass.Name);//show "Tom"
25
document.write("<br />");
26
myClass.Name = "Jerry";
27
myClass.showName();//show "Tom" "Jerry"
28
-->
29
</script>
30
</body>
31
</html>
示例1-2:该例子演示了public和private访问限定的实现。本例子中,我们修改了上个例子中的MyClass类,将其扩充为两个属性和两个方法。注意到PrivateName的声明,它并没有绑定到这个类,因此只具有局部作用域,生命期仅在本类中,如果在类的方法中调用它的话,是可以正常调用的(调用的时候不加this关键字)。同理,没有绑定的方法也只能在类的内部调用。因此,js中的public与private限定可以通过这种方式来实现。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function MyClass(pubName, privName)
6
{
7
var PrivateName = privName;
8
this.PublicName = pubName;
9
this.showName = writePublicName;
10
11
function writePublicName()
12
{
13
document.write(this.PublicName);
14
document.write("<br />");
15
//document.write(PrivateName);
16
}
17
18
function writePrivateName()
19
{
20
document.write(PrivateName);
21
document.write("<br />");
22
}
23
24
//writePrivateName();
25
}
26
-->
27
</script>
28
</head>
29
<body>
30
<script language="Jscript">
31
<!--
32
var myClass = new MyClass("Tom", "Cat");
33
document.write(myClass.PublicName);
34
document.write("<br />");
35
//document.write(myClass.PrivateName);
36
myClass.showName();
37
//myClass.writePrivateName();
38
myClass.PublicName = "Jerry";
39
myClass.showName();
40
document.write("<br />");
41
-->
42
</script>
43
</body>
44
</html>
在这里,需要注意到一个现象,既然js把类本身作为一个构造函数,它在实例化的时候将逐个执行其中的语句,保留其中的方法定义,一直把整个函数执行完。因此,本例子中注释掉的writePrivateName();语句如果正常执行,将在MyClass类实例化的时候运行。
示例1-3:该例子演示了static限定的实现。本例子中,我们将MyClass类修改为带一个普通属性、一个静态属性和两个静态方法的类。这个类可以被实例化,所能够访问的仅仅是在类中定义的普通属性。当不实例化,通过类名访问的时候,可以访问类中的静态方法和静态属性,静态方法只能访问静态属性。与C++类似,静态成员的初始化需要在类的外面进行。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function MyClass()
6
{
7
this.Name = "Cat";
8
}
9
10
MyClass.Number = 0;
11
12
MyClass.addNumber = function()
13
{
14
MyClass.Number ++;
15
document.write("One had been added to Number.");
16
document.write("<br />");
17
};
18
19
MyClass.showNumber = function()
20
{
21
MyClass.addNumber();
22
document.write(MyClass.Number);
23
document.write("<br />");
24
//document.write(MyClass.Name);
25
};
26
-->
27
</script>
28
</head>
29
<body>
30
<script language="Jscript">
31
<!--
32
MyClass.addNumber();
33
MyClass.showNumber();
34
var myClass = new MyClass();
35
document.write(myClass.Name);
36
document.write("<br />");
37
//document.write(myClass.Number);
38
-->
39
</script>
40
</body>
41
</html>
通过以上三个示例,我们能够看出,js很好地支持了封装,并且支持了基本的访问限定。
二、Js中的继承
继承是面向对象语言中扩展已有类型的一种有效途径,js没有提供用于实现继承的extends关键字或者“:”操作符,但是,由于它是一种动态语言,可以在需要的时候添加属性和方法。以下示例中继承机制的实现均基于这种原理。
示例2-1:该例子演示了从基类派生出一个子类的方法。子类中创建了基类的对象,为它添加了新的属性和方法,然后将它作为子类构造函数的结果返回出去,得到子类对象。子类对象比基类对象多了一些属性和方法,分配到了更多内存空间。注意到每次输出的constructor属性的值,发现它们两次返回的都是基类的构造函数,这并不奇怪,因为子类的构造函数最后返回的是添加了新属性、新方法的基类对象,js执行环境仍然会认为它是一个基类对象。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function BaseClass(name)
6
{
7
this.TypeName = name;
8
this.writeBaseName = writeBaseName;
9
10
function writeBaseName()
11
{
12
document.write("Base Name: ");
13
document.write(this.TypeName);
14
document.write("<br />");
15
}
16
}
17
18
function DeriveClass(name)
19
{
20
var base = new BaseClass("Cat");
21
base.Name = name;
22
base.writeDeriveName = writeDeriveName;
23
24
function writeDeriveName()
25
{
26
document.write("Derived Name: ");
27
document.write(base.Name);
28
document.write("<br />");
29
30
}
31
32
return base;
33
}
34
-->
35
</script>
36
</head>
37
<body>
38
<script language="Jscript">
39
<!--
40
var cat = new DeriveClass("Tom");
41
cat.writeBaseName();
42
cat.writeDeriveName();
43
-->
44
</script>
45
</body>
46
</html>
示例2-2:该例子演示了从两个不同基类派生出一个子类的方法。子类中分别创建了两个基类的对象,将其中一个的所有属性和方法添加到另外一个上,然后将后者作为子类构造函数的结果返回出去,得到子类对象。通过这种途径,变相地实现了多重继承。这里有一个注意点,不同的基类中如果包含同名变量或方法,需要自己指定以哪个作为实际绑定。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function Apple()
6
{
7
this.SmellLike = "Apple";
8
this.showSmell = showSmell;
9
10
function showSmell()
11
{
12
document.write("Smell like: ");
13
document.write(this.SmellLike);
14
document.write("<br />");
15
}
16
}
17
18
function Pear()
19
{
20
this.TasteLike = "Pear";
21
this.showTaste = showTaste;
22
23
function showTaste()
24
{
25
document.write("Taste like: ");
26
document.write(this.TasteLike);
27
document.write("<br />");
28
}
29
}
30
31
function ApplePear()
32
{
33
var result = new Apple();
34
var pear = new Pear();
35
result.TasteLike = pear.TasteLike;
36
result.showTaste = pear.showTaste;
37
38
return result;
39
}
40
-->
41
</script>
42
</head>
43
<body>
44
<script language="Jscript">
45
<!--
46
var applePear = new ApplePear();
47
applePear.showSmell();
48
applePear.showTaste();
49
-->
50
</script>
51
</body>
52
</html>
通过以上两个示例能够看出,js其实支持了继承的机制,只不过这种机制需要更多的手工控制。
既然提到继承,顺便讨论一下protected限定,它的作用是使得被限定的变量或方法不能被类的实例调用,但是能够被派生类调用。写这篇文章的时候本人曾经花了不少时间来考虑是否有可能使用js模拟出这种限定,后来发现,既然public跟private限定是通过指定绑定来实现的,那么,没法创建一种介于绑定和不绑定之间的关系,也就不好用这种方式模拟。再转念一想,protected限定几乎都是实现在编译型的语言中。在目标代码或者中间代码层次,这种限定非常重要,它能够有效配合软件工程理论进行开发,但是js是纯解释型语言,源代码公开的级别,不存在有效的模块保护机制,任何人可以随意修改代码,这样,protected关键字完全没有用武之地。
C++等语言拥有一种特殊类:接口。接口的作用是为类提供规范、约束,它规定了继承这个接口所必须实现的方法集合。我们当然可以实现这样一个机制,当类继承接口的时候,检查它是否实现了接口中的全部方法,或者是否提供了这些方法的声明,但这种做法并不必要。当继承这个接口的时候,根据上面实现的这种继承机制,将会直接创建接口的一个对象(有的语言中禁止把接口实例化),这个时候,它已经包含了接口中所定义的方法集合,即使不重新定义这些方法并绑定,他们显然已经被声明了,而且,js不提供一个机制用于识别抽象方法,接口中的方法是否已被实现也无从得知。
三、Js中的多态
多态能够让对象在运行时决定实际调用的方法体。由于js是一种动态语言,支持运行时绑定,讨论它的多态实际上并没有太大意义。Js不支持virtual关键字,而virtual关键字在现在的js里面也不会有明显用处。最根本的一点,js能够在运行时改变数据类型,可以随时根据新类型来取得它所拥有的方法,而忽视原有类型的影响。
示例3-1:该例子创建了一个基类跟一个子类,拥有一个同名方法,分别将基类和子类实例化并调用这个方法,显示的结果将随着方法所属类的不同而不同。原因很简单,因为例子使用的这种“继承”机制本身就是利用js语言的动态特性实现的,在一个类实例化的时候,它已经知道自己拥有哪些方法,而且,它也不关心这些方法从哪个类继承,只是将它们作为普通的成员方法来调用,所以,每一个类调用的同名方法都是属于自己的那个。再者,js是弱类型语言,它的变量声明的时候并不知道自己的类型,也并不知道将要被初始化成什么类型,赋予它什么类型的值,它就是什么类型的变量。本例子中分别注释掉的两段语句就说明了这个问题:Js在运行时既能够将子类变量赋给基类变量(这个特性在C++等语言中正是实现多态的关键),也可以将基类变量赋给子类变量(一般在静态语言中不允许,因为缺少附加信息)。


1
<html>
2
<head>
3
<script language="Jscript">
4
<!--
5
function BaseClass(name)
6
{
7
this.TypeName = name;
8
this.writeName = writeName;
9
10
function writeName()
11
{
12
document.write("Base Name: ");
13
document.write(this.TypeName);
14
document.write("<br />");
15
document.write(this.constructor);
16
document.write("<br />");
17
}
18
}
19
20
function DeriveClass(name)
21
{
22
var base = new BaseClass("Cat");
23
base.Name = name;
24
base.writeName = writeName;
25
26
function writeName()
27
{
28
document.write("Derived Name: ");
29
document.write(base.Name);
30
document.write("<br />");
31
document.write(this.constructor);
32
document.write("<br />");
33
}
34
35
return base;
36
}
37
-->
38
</script>
39
</head>
40
<body>
41
<script language="Jscript">
42
<!--
43
var base = new BaseClass("Cat");
44
base.writeName();
45
var derive = new DeriveClass("Tom");
46
derive.writeName();
47
/**//*
48
base = derive;
49
base.writeName();
50
*/
51
/**//*
52
derive = base;
53
derive.writeName();
54
*/
55
-->
56
</script>
57
</body>
58
</html>
四、总结
综合以上分析,js勉强能够算是一种面向对象的语言,因此,也可以将一些软件工程的思想应用到开发中,使得整个项目的架构稳定,逻辑清晰。
一、Js中的封装
一种面向对象的语言应当允许用户创建自定义类型,这一点js做到了,只是它的自定义类型不是用class限定,而是作为function,这个function的作用相当于其他语言中的构造函数。在自定义类型中,用户可以添加属性、方法。但是,js中并未显式提供public、private、protected等访问限定,也没有提供static之类的作用域限定。下面通过几个例子,逐一说明创建自定义类型及实现各种限定的方法。
示例1-1:该例子演示了创建及使用包含属性和方法的自定义类型的过程。本例子中,我们创建了一个名为MyClass的类,其中包含了一个Name属性和一个showName方法,然后将这个类实例化,即可调用其中的方法,使用其中的属性。注意,在类中定义的方法和属性应当使用this关键字绑定到这个类,调用时使用绑定名字来访问,而不是通过实际名字。
1
<html>2
<head>3
<script language="Jscript">4
<!--5
function MyClass(name)6

{7
this.Name = name;8
this.showName = writeName;9

10
function writeName()11

{12
document.write(this.Name);13
document.write("<br />");14
}15
}16
-->17
</script>18
</head>19
<body>20
<script language="Jscript">21
<!--22
var myClass = new MyClass("Tom");23
myClass.showName();24
document.write(myClass.Name);//show "Tom"25
document.write("<br />");26
myClass.Name = "Jerry";27
myClass.showName();//show "Tom" "Jerry"28
-->29
</script>30
</body>31
</html>1
<html>2
<head>3
<script language="Jscript">4
<!--5
function MyClass(pubName, privName)6

{7
var PrivateName = privName;8
this.PublicName = pubName;9
this.showName = writePublicName;10

11
function writePublicName()12

{13
document.write(this.PublicName);14
document.write("<br />");15
//document.write(PrivateName);16
}17

18
function writePrivateName()19

{20
document.write(PrivateName);21
document.write("<br />");22
}23

24
//writePrivateName();25
}26
-->27
</script>28
</head>29
<body>30
<script language="Jscript">31
<!--32
var myClass = new MyClass("Tom", "Cat");33
document.write(myClass.PublicName);34
document.write("<br />");35
//document.write(myClass.PrivateName);36
myClass.showName();37
//myClass.writePrivateName();38
myClass.PublicName = "Jerry";39
myClass.showName();40
document.write("<br />");41
-->42
</script>43
</body>44
</html>示例1-3:该例子演示了static限定的实现。本例子中,我们将MyClass类修改为带一个普通属性、一个静态属性和两个静态方法的类。这个类可以被实例化,所能够访问的仅仅是在类中定义的普通属性。当不实例化,通过类名访问的时候,可以访问类中的静态方法和静态属性,静态方法只能访问静态属性。与C++类似,静态成员的初始化需要在类的外面进行。
1
<html>2
<head>3
<script language="Jscript">4
<!--5
function MyClass()6

{7
this.Name = "Cat";8
}9
10
MyClass.Number = 0;11

12
MyClass.addNumber = function()13

{14
MyClass.Number ++;15
document.write("One had been added to Number.");16
document.write("<br />");17
};18

19
MyClass.showNumber = function()20

{21
MyClass.addNumber();22
document.write(MyClass.Number);23
document.write("<br />");24
//document.write(MyClass.Name);25
};26
-->27
</script>28
</head>29
<body>30
<script language="Jscript">31
<!--32
MyClass.addNumber();33
MyClass.showNumber();34
var myClass = new MyClass();35
document.write(myClass.Name);36
document.write("<br />");37
//document.write(myClass.Number);38
-->39
</script>40
</body>41
</html>二、Js中的继承
继承是面向对象语言中扩展已有类型的一种有效途径,js没有提供用于实现继承的extends关键字或者“:”操作符,但是,由于它是一种动态语言,可以在需要的时候添加属性和方法。以下示例中继承机制的实现均基于这种原理。
示例2-1:该例子演示了从基类派生出一个子类的方法。子类中创建了基类的对象,为它添加了新的属性和方法,然后将它作为子类构造函数的结果返回出去,得到子类对象。子类对象比基类对象多了一些属性和方法,分配到了更多内存空间。注意到每次输出的constructor属性的值,发现它们两次返回的都是基类的构造函数,这并不奇怪,因为子类的构造函数最后返回的是添加了新属性、新方法的基类对象,js执行环境仍然会认为它是一个基类对象。
1
<html>2
<head>3
<script language="Jscript">4
<!--5
function BaseClass(name)6

{7
this.TypeName = name;8
this.writeBaseName = writeBaseName;9

10
function writeBaseName()11

{12
document.write("Base Name: ");13
document.write(this.TypeName);14
document.write("<br />");15
}16
}17

18
function DeriveClass(name)19

{20
var base = new BaseClass("Cat");21
base.Name = name;22
base.writeDeriveName = writeDeriveName;23

24
function writeDeriveName()25

{26
document.write("Derived Name: ");27
document.write(base.Name);28
document.write("<br />");29

30
}31

32
return base;33
}34
-->35
</script>36
</head>37
<body>38
<script language="Jscript">39
<!--40
var cat = new DeriveClass("Tom");41
cat.writeBaseName();42
cat.writeDeriveName();43
-->44
</script>45
</body>46
</html>1
<html>2
<head>3
<script language="Jscript">4
<!--5
function Apple()6

{7
this.SmellLike = "Apple";8
this.showSmell = showSmell;9

10
function showSmell()11

{12
document.write("Smell like: ");13
document.write(this.SmellLike);14
document.write("<br />");15
}16
}17

18
function Pear()19

{20
this.TasteLike = "Pear";21
this.showTaste = showTaste;22

23
function showTaste()24

{25
document.write("Taste like: ");26
document.write(this.TasteLike);27
document.write("<br />");28
}29
}30

31
function ApplePear()32

{33
var result = new Apple();34
var pear = new Pear();35
result.TasteLike = pear.TasteLike;36
result.showTaste = pear.showTaste;37

38
return result;39
}40
-->41
</script>42
</head>43
<body>44
<script language="Jscript">45
<!--46
var applePear = new ApplePear();47
applePear.showSmell();48
applePear.showTaste();49
-->50
</script>51
</body>52
</html>既然提到继承,顺便讨论一下protected限定,它的作用是使得被限定的变量或方法不能被类的实例调用,但是能够被派生类调用。写这篇文章的时候本人曾经花了不少时间来考虑是否有可能使用js模拟出这种限定,后来发现,既然public跟private限定是通过指定绑定来实现的,那么,没法创建一种介于绑定和不绑定之间的关系,也就不好用这种方式模拟。再转念一想,protected限定几乎都是实现在编译型的语言中。在目标代码或者中间代码层次,这种限定非常重要,它能够有效配合软件工程理论进行开发,但是js是纯解释型语言,源代码公开的级别,不存在有效的模块保护机制,任何人可以随意修改代码,这样,protected关键字完全没有用武之地。
C++等语言拥有一种特殊类:接口。接口的作用是为类提供规范、约束,它规定了继承这个接口所必须实现的方法集合。我们当然可以实现这样一个机制,当类继承接口的时候,检查它是否实现了接口中的全部方法,或者是否提供了这些方法的声明,但这种做法并不必要。当继承这个接口的时候,根据上面实现的这种继承机制,将会直接创建接口的一个对象(有的语言中禁止把接口实例化),这个时候,它已经包含了接口中所定义的方法集合,即使不重新定义这些方法并绑定,他们显然已经被声明了,而且,js不提供一个机制用于识别抽象方法,接口中的方法是否已被实现也无从得知。
三、Js中的多态
多态能够让对象在运行时决定实际调用的方法体。由于js是一种动态语言,支持运行时绑定,讨论它的多态实际上并没有太大意义。Js不支持virtual关键字,而virtual关键字在现在的js里面也不会有明显用处。最根本的一点,js能够在运行时改变数据类型,可以随时根据新类型来取得它所拥有的方法,而忽视原有类型的影响。
示例3-1:该例子创建了一个基类跟一个子类,拥有一个同名方法,分别将基类和子类实例化并调用这个方法,显示的结果将随着方法所属类的不同而不同。原因很简单,因为例子使用的这种“继承”机制本身就是利用js语言的动态特性实现的,在一个类实例化的时候,它已经知道自己拥有哪些方法,而且,它也不关心这些方法从哪个类继承,只是将它们作为普通的成员方法来调用,所以,每一个类调用的同名方法都是属于自己的那个。再者,js是弱类型语言,它的变量声明的时候并不知道自己的类型,也并不知道将要被初始化成什么类型,赋予它什么类型的值,它就是什么类型的变量。本例子中分别注释掉的两段语句就说明了这个问题:Js在运行时既能够将子类变量赋给基类变量(这个特性在C++等语言中正是实现多态的关键),也可以将基类变量赋给子类变量(一般在静态语言中不允许,因为缺少附加信息)。
1
<html>2
<head>3
<script language="Jscript">4
<!--5
function BaseClass(name)6

{7
this.TypeName = name;8
this.writeName = writeName;9

10
function writeName()11

{12
document.write("Base Name: ");13
document.write(this.TypeName);14
document.write("<br />");15
document.write(this.constructor);16
document.write("<br />");17
}18
}19

20
function DeriveClass(name)21

{22
var base = new BaseClass("Cat");23
base.Name = name;24
base.writeName = writeName;25

26
function writeName()27

{28
document.write("Derived Name: ");29
document.write(base.Name);30
document.write("<br />");31
document.write(this.constructor);32
document.write("<br />");33
}34

35
return base;36
}37
-->38
</script>39
</head>40
<body>41
<script language="Jscript">42
<!--43
var base = new BaseClass("Cat");44
base.writeName();45
var derive = new DeriveClass("Tom");46
derive.writeName();47

/**//*48
base = derive;49
base.writeName();50
*/51

/**//*52
derive = base;53
derive.writeName();54
*/55
-->56
</script>57
</body>58
</html>综合以上分析,js勉强能够算是一种面向对象的语言,因此,也可以将一些软件工程的思想应用到开发中,使得整个项目的架构稳定,逻辑清晰。
