欢迎光临

木子屋

标题: 面向对象的Jscript

作者: dnawo

日期: 2007-07-09 10:35 AM

分类: 脚本语言

内容: 　　在web页面的客户端应用中，js已经成为不可缺少的部分。传统方式对js的应用完全基于过程模型，在这种模型里，普通语句及全局函数的使用最为普遍。当代码的数量逐渐增加，整个项目的维护就变得困难，逻辑也渐渐超出设计者的掌控，这个时候，我们需要借用软件工程的观念来管理项目。现代软件工程的根基是组件化、对象化的程序设计，由UML设计图指导的程序设计过程有条不紊地进行着。令人苦恼的是，当现代软件工程的理念渗入web项目的时候，却遇到了很大的问题，几乎没有办法发挥它的威力。
问题的根源是什么？是我们没有一种有效的方式来组织js程序，使得它能够遵循一些基本的面向对象思想。然而，js并非没有办法体现出这些思想，本文试图用某些特殊的组织方式来让js符合基本的面向对象特性，为进一步应用软件工程的某些设计模式作铺垫。
Jscript中内置了一些类，例如String、Array、Math等，用户可以直接从这些类实例化出对象，并使用其中的属性和方法。单凭这一点，不能说js符合面向对象语言的特征。一种面向对象的语言，应当具有封装，继承，多态等基本特性。Jscript并没有直接提供实现这些特性的方法，但是，也并非完全无法实现。
一、Js中的封装
一种面向对象的语言应当允许用户创建自定义类型，这一点js做到了，只是它的自定义类型不是用class限定，而是作为function，这个function的作用相当于其他语言中的构造函数。在自定义类型中，用户可以添加属性、方法。但是，js中并未显式提供public、private、protected等访问限定，也没有提供static之类的作用域限定。下面通过几个例子，逐一说明创建自定义类型及实现各种限定的方法。
示例1-1：该例子演示了创建及使用包含属性和方法的自定义类型的过程。本例子中，我们创建了一个名为MyClass的类，其中包含了一个Name属性和一个showName方法，然后将这个类实例化，即可调用其中的方法，使用其中的属性。注意，在类中定义的方法和属性应当使用this关键字绑定到这个类，调用时使用绑定名字来访问，而不是通过实际名字。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function MyClass(name)
{
this.Name = name;
this.showName = writeName;
function writeName()
{
document.write(this.Name);
document.write("<br />");
}
}
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
var myClass = new MyClass("Tom");
myClass.showName();
document.write(myClass.Name);
document.write("<br />");
myClass.Name = "Jerry";
myClass.showName();
-->
</script>
</body>
</html>
示例1-2：该例子演示了public和private访问限定的实现。本例子中，我们修改了上个例子中的MyClass类，将其扩充为两个属性和两个方法。注意到PrivateName的声明，它并没有绑定到这个类，因此只具有局部作用域，生命期仅在本类中，如果在类的方法中调用它的话，是可以正常调用的（调用的时候不加this关键字）。同理，没有绑定的方法也只能在类的内部调用。因此，js中的public与private限定可以通过这种方式来实现。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function MyClass(pubName, privName)
{
var PrivateName = privName;
this.PublicName = pubName;
this.showName = writePublicName;
function writePublicName()
{
document.write(this.PublicName);
document.write("<br />");
//document.write(PrivateName);
}
function writePrivateName()
{
document.write(PrivateName);
document.write("<br />");
}
//writePrivateName();
}
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
var myClass = new MyClass("Tom", "Cat");
document.write(myClass.PublicName);
document.write("<br />");
//document.write(myClass.PrivateName);
myClass.showName();
//myClass.writePrivateName();
myClass.PublicName = "Jerry";
myClass.showName();
document.write("<br />");
-->
</script>
</body>
</html>
在这里，需要注意到一个现象，既然js把类本身作为一个构造函数，它在实例化的时候将逐个执行其中的语句，保留其中的方法定义，一直把整个函数执行完。因此，本例子中注释掉的writePrivateName();语句如果正常执行，将在MyClass类实例化的时候运行。
示例1-3：该例子演示了static限定的实现。本例子中，我们将MyClass类修改为带一个普通属性、一个静态属性和两个静态方法的类。这个类可以被实例化，所能够访问的仅仅是在类中定义的普通属性。当不实例化，通过类名访问的时候，可以访问类中的静态方法和静态属性，静态方法只能访问静态属性。与C＋＋类似，静态成员的初始化需要在类的外面进行。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function MyClass()
{
this.Name = "Cat";
}

MyClass.Number = 0;
MyClass.addNumber = function()
{
MyClass.Number ++;
document.write("One had been added to Number.");
document.write("<br />");
};
MyClass.showNumber = function()
{
MyClass.addNumber();
document.write(MyClass.Number);
document.write("<br />");
//document.write(MyClass.Name);
};
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
MyClass.addNumber();
MyClass.showNumber();
var myClass = new MyClass();
document.write(myClass.Name);
document.write("<br />");
//document.write(myClass.Number);
-->
</script>
</body>
</html>
通过以上三个示例，我们能够看出，js很好地支持了封装，并且支持了基本的访问限定。
二、Js中的继承
继承是面向对象语言中扩展已有类型的一种有效途径，js没有提供用于实现继承的extends关键字或者“:”操作符，但是，由于它是一种动态语言，可以在需要的时候添加属性和方法。以下示例中继承机制的实现均基于这种原理。
示例2-1：该例子演示了从基类派生出一个子类的方法。子类中创建了基类的对象，为它添加了新的属性和方法，然后将它作为子类构造函数的结果返回出去，得到子类对象。子类对象比基类对象多了一些属性和方法，分配到了更多内存空间。注意到每次输出的constructor属性的值，发现它们两次返回的都是基类的构造函数，这并不奇怪，因为子类的构造函数最后返回的是添加了新属性、新方法的基类对象，js执行环境仍然会认为它是一个基类对象。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function BaseClass(name)
{
this.TypeName = name;
this.writeBaseName = writeBaseName;
function writeBaseName()
{
document.write("Base Name: ");
document.write(this.TypeName);
document.write("<br />");
document.write(this.constructor);
document.write("<br />");
}
}
function DeriveClass(name)
{
var base = new BaseClass("Cat");
base.Name = name;
base.writeDeriveName = writeDeriveName;
function writeDeriveName()
{
document.write("Derived Name: ");
document.write(base.Name);
document.write("<br />");
document.write(this.constructor);
document.write("<br />");
}
return base;
}
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
var cat = new DeriveClass("Tom");
cat.writeBaseName();
cat.writeDeriveName();
-->
</script>
</body>
</html>
示例2-2：该例子演示了从两个不同基类派生出一个子类的方法。子类中分别创建了两个基类的对象，将其中一个的所有属性和方法添加到另外一个上，然后将后者作为子类构造函数的结果返回出去，得到子类对象。通过这种途径，变相地实现了多重继承。这里有一个注意点，不同的基类中如果包含同名变量或方法，需要自己指定以哪个作为实际绑定。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function Apple()
{
this.SmellLike = "Apple";
this.showSmell = showSmell;
function showSmell()
{
document.write("Smell like: ");
document.write(this.SmellLike);
document.write("<br />");
}
}
function Pear()
{
this.TasteLike = "Pear";
this.showTaste = showTaste;
function showTaste()
{
document.write("Taste like: ");
document.write(this.TasteLike);
document.write("<br />");
}
}
function ApplePear()
{
var result = new Apple();
var pear = new Pear();
result.TasteLike = pear.TasteLike;
result.showTaste = pear.showTaste;
return result;
}
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
var applePear = new ApplePear();
applePear.showSmell();
applePear.showTaste();
-->
</script>
</body>
</html>
通过以上两个示例能够看出，js其实支持了继承的机制，只不过这种机制需要更多的手工控制。
既然提到继承，顺便讨论一下protected限定，它的作用是使得被限定的变量或方法不能被类的实例调用，但是能够被派生类调用。写这篇文章的时候本人曾经花了不少时间来考虑是否有可能使用js模拟出这种限定，后来发现，既然public跟private限定是通过指定绑定来实现的，那么，没法创建一种介于绑定和不绑定之间的关系，也就不好用这种方式模拟。再转念一想，protected限定几乎都是实现在编译型的语言中。在目标代码或者中间代码层次，这种限定非常重要，它能够有效配合软件工程理论进行开发，但是js是纯解释型语言，源代码公开的级别，不存在有效的模块保护机制，任何人可以随意修改代码，这样，protected关键字完全没有用武之地。
C＋＋等语言拥有一种特殊类：接口。接口的作用是为类提供规范、约束，它规定了继承这个接口所必须实现的方法集合。我们当然可以实现这样一个机制，当类继承接口的时候，检查它是否实现了接口中的全部方法，或者是否提供了这些方法的声明，但这种做法并不必要。当继承这个接口的时候，根据上面实现的这种继承机制，将会直接创建接口的一个对象（有的语言中禁止把接口实例化），这个时候，它已经包含了接口中所定义的方法集合，即使不重新定义这些方法并绑定，他们显然已经被声明了，而且，js不提供一个机制用于识别抽象方法，接口中的方法是否已被实现也无从得知。
三、Js中的多态
多态能够让对象在运行时决定实际调用的方法体。由于js是一种动态语言，支持运行时绑定，讨论它的多态实际上并没有太大意义。Js不支持virtual关键字，而virtual关键字在现在的js里面也不会有明显用处。最根本的一点，js能够在运行时改变数据类型，可以随时根据新类型来取得它所拥有的方法，而忽视原有类型的影响。
示例3-1：该例子创建了一个基类跟一个子类，拥有一个同名方法，分别将基类和子类实例化并调用这个方法，显示的结果将随着方法所属类的不同而不同。原因很简单，因为例子使用的这种“继承”机制本身就是利用js语言的动态特性实现的，在一个类实例化的时候，它已经知道自己拥有哪些方法，而且，它也不关心这些方法从哪个类继承，只是将它们作为普通的成员方法来调用，所以，每一个类调用的同名方法都是属于自己的那个。再者，js是弱类型语言，它的变量声明的时候并不知道自己的类型，也并不知道将要被初始化成什么类型，赋予它什么类型的值，它就是什么类型的变量。本例子中分别注释掉的两段语句就说明了这个问题：Js在运行时既能够将子类变量赋给基类变量（这个特性在C＋＋等语言中正是实现多态的关键），也可以将基类变量赋给子类变量（一般在静态语言中不允许，因为缺少附加信息）。
复制内容到剪贴板 程序代码<html>
<head>
<script language="Jscript">
<!--
function BaseClass(name)
{
this.TypeName = name;
this.writeName = writeName;
function writeName()
{
document.write("Base Name: ");
document.write(this.TypeName);
document.write("<br />");
document.write(this.constructor);
document.write("<br />");
}
}
function DeriveClass(name)
{
var base = new BaseClass("Cat");
base.Name = name;
base.writeName = writeName;
function writeName()
{
document.write("Derived Name: ");
document.write(base.Name);
document.write("<br />");
document.write(this.constructor);
document.write("<br />");
}
return base;
}
-->
</script>
</head>
<body>
<script language="Jscript">
<!--
var base = new BaseClass("Cat");
base.writeName();
var derive = new DeriveClass("Tom");
derive.writeName();
/*
base = derive;
base.writeName();
*/
/*
derive = base;
derive.writeName();
*/
-->
</script>
</body>
</html>
四、总结
综合以上分析，js勉强能够算是一种面向对象的语言，因此，也可以将一些软件工程的思想应用到开发中，使得整个项目的架构稳定，逻辑清晰。51js论坛的万常华前辈作出了很大的努力，他将js的面向对象特性设计成一种规则，模仿java的包－类结构，做出了一些由这种规则支撑的常用类库，经过一段时间的努力和完善，已经可以初步进行应用了。
附记：写这篇文章来源于一个灵感，在学习设计模式的时候，我联想到目前web程序设计中的诸多不规范情况，多数人依然使用传统的过程化结构来控制整个项目，当项目越做越大的时候常有力不从心的感觉。混乱的设计、到处隐藏的bug，这一切注定无法避免么？于是，我尝试用面向对象的思路来重新看待js，并期望以后能够在这个基础上，将一些设计模式应用到web程序设计上来，在界面设计与事务处理分离的前提下，让整个项目的架构趋于合理、稳定。写这篇文章的时候，我并不知道到底我对js了解了多少，也不知道我的理解是否包含了严重的错误，这一切只是一个探索，也希望大家能够赐教，或者给出好的建议。本人是东南大学计算机系大四的学生，碰巧的是，这学期软件体系结构课程老师要求自由选题写一篇论文，花了一个星期时间，希望没有让老师失望，呵呵。

+ 查看当前日志评论 (0)

登录

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 0.813 ms

返回

标题: 面向对象的Jscript

你没有权限发表评论

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 0.813 ms

返回

暂无评论

返回

────────────

木子屋

PJBlog3 v3.2.9.518 Inside.

Processed In 0.828 ms

返回