From:http://www.ajaxwing.com/index.php?id=2
一,背景
回顾一下编程语言的发展,不难发现这是一个不断封装的过程:从最开始的汇编语言,到面向过程语言,然后到面向对象语言,再到具备面向对象特性的脚本语言,一层一层封装,一步一步减轻程序员的负担,逐渐提高编写程序的效率。这篇文章是关于JavaScript的,所以我们先来了解一下JavaScript是一种怎样的语言。到目前为止,JavaScript是一种不完全支持面向对象特性的脚本语言。之所以这样说是因为JavaScript的确支持对象的概念,在程序中我们看到都是对象,可是Javascipt并不支持类的封装和继承。曾经有过C++、Java或者php、python编程经验的读者都会知道,这些语言允许我们使用类来设计对象,并且这些类是可继承的。JavaScript的确支持自定义对象和继承,不过使用的是另外一种方式:prototype(中文译作:原型)。用过JavaScript的或者读过《设计模式》的读者都会了解这种技术,描述如下:
每个对象都包含一个prototype对象,当向对象查询一个属性或者请求一个方法的时候,运行环境会先在当前对象中查找,如果查找失败则查找其prototype对象。注意prototype也是一个对象,于是这种查找过程同样适用在对象的prototype对象中,直到当前对象的prototpye为空。
在JavaScript中,对象的prototype在运行期是不可见的,只能在定义对象的构造函数时,创建对象之前设定。下面的用法都是错误的:
o2.prototype=o1;
/*
这时只定义了o2的一个名为“prototype”的属性,
并没有将o1设为o2的prototype。
*/
//---------------
f2=function(){};
o2=newf2;
f2.prototype=o1;
/*
这时o1并没有成为o2的prototype,
因为o2在f2设定prototype之前已经被创建。
*/
//---------------
f1=function(){};
f2=function(){};
o1=newf1;
f2.prototype=o1;
o2=newf2;
/*
同样,这时o1并不是o2的prototype,
因为JavaScript不允许构造函数的prototype对象被其它变量直接引用。
*/
正确的用法应该是:
f1=function(){};
f2=function(){};
f2.prototype=newf1;
o2=newf2;
从上面的例子可以看出:如果你想让构造函数F2继承另外一个构造函数F1所定义的属性和方法,那么你必须先创建一个F1的实例对象,并立刻将其设为F2的prototype。于是你会发现使用prototype这种继承方法实际上是不鼓励使用继承:一方面是由于JavaScript被设计成一种嵌入式脚本语言,比方说嵌入到浏览器中,用它编写的应用一般不会很大很复杂,不需要用到继承;另一方面如果继承得比较深,prototype链就会比较长,用在查找对象属性和方法的时间就会变长,降低程序的整体运行效率。
二,问题
现在JavaScript的使用场合越来越多,web2.0有一个很重要的方面就是用户体验。好的用户体验不但要求美工做得好,并且讲求响应速度和动态效果。很多有名的web2.0应用都使用了大量的JavaScript代码,比方说Flickr、Gmail等等。甚至有些人用Javasript来编写基于浏览器的GUI,比方说Backbase、Qooxdoo等等。于是JavaScript代码的开发和维护成了一个很重要的问题。很多人都不喜欢自己发明轮子,他们希望JavaScript可以像其它编程语言一样,有一套成熟稳定Javasript库来提高他们的开发速度和效率。更多人希望的是,自己所写的JavaScript代码能够像其它面向对象语言写的代码一样,具有很好的模块化特性和很好的重用性,这样维护起来会更方便。可是现在的JavaScript并没有很好的支持这些需求,大部分开发都要重头开始,并且维护起来很不方便。
三,已有解决方案
有需求自然就会有解决方案,比较成熟的有两种:
1,现在很多人在自己的项目中使用一套叫prototype.js的JavaScript库,那是由MVCweb框架RubyonRails开发并使用JavaScript基础库。这套库设计精良并且具有很好的可重用性和跨浏览器特性,使用prototype.js可以大大简化客户端代码的开发工作。prototype.js引入了类的概念,用其编写的类可以定义一个initialize的初始化函数,在创建类实例的时候会首先调用这个初始化函数。正如其名字,prototype.js的核心还是prototype,虽然提供了很多可复用的代码,但没有从根本上解决JavaScript的开发和维护问题。
2,使用asp.net的人一般都会听过或者用到一个叫Atlas的框架,那是微软的AJAX利器。Atlas允许客户端代码用类的方法来编写,并且比prototype.js具备更好的面向对象特性,比方说定义类的私有属性和私有方法、支持继承、像java那样编写接口等等。Atlas是一个从客户端到服务端的解决方案,但只能在asp.net中使用、版权等问题限制了其使用范围。
从根本上解决问题只有一个,就是等待JavaScript2.0(或者说ECMAScript4.0)标准的出台。在下一版本的JavaScript中已经从语言上具备面向对象的特性。另外,微软的JScript.NET已经可以使用这些特性。当然,等待不是一个明智的方法。
四,Modello框架
如果上面的表述让你觉得有点头晕,最好不要急于了解Modello框架,先保证这几个概念你已经能够准确理解:
JavaScript构造函数:在JavaScript中,自定义对象通过构造函数来设计。运算符new加上构造函数就会创建一个实例对象
JavaScript中的prototype:如果将一个对象P设定为一个构造函数F的prototype,那么使用F创建的实例对象就会继承P的属性和方法
类:面向对象语言使用类来封装和设计对象。按类型分,类的成员分为属性和方法。按访问权限分,类的成员分为静态成员,私有成员,保护成员,公有成员
类的继承:面向对象语言允许一个类继承另外一个类的属性和方法,继承的类叫做子类,被继承的类叫做父类。某些语言允许一个子类只能继承一个父类(单继承),某些语言则允许继承多个(多继承)
JavaScript中的closure特性:函数的作用域就是一个closure。JavaScript允许在函数O中定义内部函数I,内部函数I总是可以访问其外部函数O中定义的变量。即使在外部函数O返回之后,你再调用内部函数I,同样可以访问外部函数O中定义的变量。也就是说,如果你在构造函数C中用var定义了一个变量V,用this定义了一个函数F,由C创建的实例对象O调用O.F时,F总是可以访问到V,但是用O.V这样来访问却不行,因为V不是用this来定义的。换言之,V成了O的私有成员。这个特性非常重要,如果你还没有彻底搞懂,请参考这篇文章《PrivateMembersinJavaScript》
搞懂上面的概念,理解下面的内容对你来说已经没有难度,开始吧!
如题,Modello是一个允许并且鼓励你用JavaScript来编写类的框架。传统的JavaScript使用构造函数来自定义对象,用prototype来实现继承。在Modello中,你可以忘掉晦涩的prototype,因为Modello使用类来设计对象,用类来实现继承,就像其它面向对象语言一样,并且使用起来更加简单。不信吗?请继续往下看。
使用Modello编写的类所具备如下特性:
私有成员、公共成员和静态成员
类的继承,多继承
命名空间
类型鉴别
Modello还具有以下特性:
更少的概念,更方便的使用方法
小巧,只有两百行左右的代码
设计期和运行期彻底分离,使用继承的时候不需要使用prototype,也不需要先创建父类的实例
兼容prototype.js的类,兼容JavaScript构造函数
跨浏览器,跨浏览器版本
开放源代码,BSDlicenced,允许免费使用在个人项目或者商业项目中
下面介绍Modello的使用方法:
1,定义一个类
Point=Class.create();
/*
创建一个类。用过prototype.js的人觉得很熟悉吧;)
*/
2,注册一个类
Point.register("Modello.Point");
/*
这里"Modello"是命名空间,"Point"是类名,之间用"."分隔
如果注册成功,
Point.namespace等于"Modello",Point.classname等于"Point"。
如果失败Modello会抛出一个异常,说明失败原因。
*/
Point.register("Point");//这里使用默认的命名空间"std"
Class.register(Point,"Point");//使用Class的register方法
3,获取已注册的类
P=Class.get("Modello.Point");
P=Class.get("Point");//这里使用默认的命名空间"std"
4,使用继承
ZPoint=Class.create(Point);//ZPoint继承Point
ZPoint=Class.create("Modello.Point");//继承已注册的类
ZPoint=Class.create(Point1,Point2[,...]);
/*
多继承。参数中的类也可以用已注册的类名来代替
*/
/*
继承关系:
Point.subclasses内容为[ZPoint]
ZPoint.superclasses内容为[Point]
*/
5,定义类的静态成员
Point.count=0;
Point.add=function(x,y){
returnx+y;
}
6,定义类的构造函数
Point.construct=function($self,$class){
//用"var"来定义私有成员
var_name="";
var_getName=function(){
return_name;
}
//用"this"来定义公有成员
this.x=0;
this.y=0;
this.initialize=function(x,y){//初始化函数
this.x=x;
this.y=y;
$class.count+=1;//访问静态成员
//公有方法访问私有私有属性
this.setName=function(name){
_name=name;
}
this.getName=function(){
return_getName();
}
this.toString=function(){
return"Point("+this.x+","+this.y+")";
}
//注意:initialize和toString方法只有定义成公有成员才生效
this.add=function(){
//调用静态方法,使用构造函数传入的$class
return$class.add(this.x,this.y);
}
}
ZPoint.construct=function($self,$class){
this.z=0;//this.x,this.y继承自Point
//重载Point的初始化函数
this.initialize=function(x,y,z){
this.z=z;
//调用第一个父类的初始化函数,
//第二个父类是$self.super1,如此类推。
//注意:这里使用的是构造函数传入的$self变量
$self.super0.initialize.call(this,x,y);
//调用父类的任何方法都可以使用这种方式,但只限于父类的公有方法
}
//重载Point的toString方法
this.toString=function(){
return"Point("+this.x+","+this.y+
","+this.z+")";
}
}
//连写技巧
Class.create().register("Modello.Point").construct=function($self,$class){
//...
}
7,创建类的实例
//两种方法:new和create
point=newPoint(1,2);
point=Point.create(1,2);
point=Class.get("Modello.Point").create(1,2);
zpoint=newZPoint(1,2,3);
8,类型鉴别
ZPoint.subclassOf(Point);//返回true
point.instanceOf(Point);//返回true
point.isA(Point);//返回true
zpoint.isA(Point);//返回true
zpoint.instanceOf(Point);//返回false
//上面的类均可替换成已注册的类名
以上就是Modello提供的全部功能。下面说说使用Modello的注意事项和建议:
在使用继承时,传入的父类可以是使用prototype.js方式定义的类或者JavaScript方式定义的构造函数
类实际上也是一个函数,普通的prototype的继承方式同样适用在用Modello定义的类中
类可以不注册,这种类叫做匿名类,不能通过Class.get方法获取
如果定义类构造函数时,像上面例子那样提供了$self,$class两个参数,Modello会在创建实例时将实例本身传给$self,将类本身传给$class。$self一般在访问父类成员时才使用,$class一般在访问静态成员时才使用。虽然$self和$class功能很强大,但不建议你在其它场合使用,除非你已经读懂Modello的源代码,并且的确有特殊需求。更加不要尝试使用$self代替this,这样可能会给你带来麻烦
子类无法访问父类的私有成员,静态方法中无法访问私有成员
Modello中私有成员的名称没有特别限制,不过用"_"开始是一个好习惯
Modello不支持保护(protected)成员,如果你想父类成员可以被子类访问,则必须将父类成员定义为公有。你也可以参考"this._property"这样的命名方式来表示保护成员:)
尽量将一些辅助性的计算复杂度大的方法定义成静态成员,这样可以提高运行效率
使用Modello的继承和类型鉴别可以实现基本的接口(interface)功能,你已经发现这一点了吧;)
使用多继承的时候,左边的父类优先级高于右边的父类。也就是说假如多个父类定义了同一个方法,最左边的父类定义的方法最终被继承
使用Modello编写的类功能可以媲美使用Atlas编写的类,并且使用起来更简洁。如果你想用Modello框架代替prototype.js中的简单类框架,只需要先包含modello.js,然后去掉prototype.js中定义Class的几行代码即可,一切将正常运行。
如果你发现Modello的bug,非常欢迎你通过email联系我。如果你觉得Modello应该具备更多功能,你可以尝试阅读一下源代码,你会发现Modello可以轻松扩展出你所需要的功能。
Modello的原意为“大型艺术作品的模型”,希望Modello能够帮助你编写高质量的JavaScript代码。
5,下载
Modello的完整参考说明和下载地址:http://modello.sourceforge.net