常见问题当前位置:星鸿娱乐 > 常见问题 > >

使用Modello编写JavaScript类

  

[使用Modello编写JavaScript类]使用Modello编写JavaScript类

  

From:  
  
  
一,背景  
回顾一下编程语言的发展,不难发现这是一个不断封装的过程:从最开始的汇编语言,到面向过程语言,然后到面向对象语言,再到具备面向对象特性的脚本语言,一层一层封装,一步一步减轻程序员的负担,逐渐提高编写程序的效率。这篇文章是关于 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 = new f2;  
f2.prototype = o1;  
/*  
这时 o1 并没有成为 o2 的 prototype,  
因为 o2 在 f2 设定 prototype 之前已经被创建。

  
*/  
  
// ---------------  
  
f1 = function(){};  
f2 = function(){};  
o1 = new f1;  
f2.prototype = o1;  
o2 = new f2;  
/*  
同样,这时 o1 并不是 o2 的 prototype,  
因为 JavaScript 不允许构造函数的 prototype 对象被其它变量直接引用。

  
*/  
  
正确的用法应该是:  
  
f1 = function(){};  
f2 = function(){};  
f2.prototype = new f1;  
o2 = new f2;  
  
从上面的例子可以看出:如果你想让构造函数 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 库,那是由 MVC web 框架 Ruby on Rails 开发并使用 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 的私有成员。这个特性非常重要,如果你还没有彻底搞懂,请参考这篇文章《Private Members in JavaScript》  
搞懂上面的概念,理解下面的内容对你来说已经没有难度,开始吧!

  
  
如题,Modello 是一个允许并且鼓励你用 JavaScript 来编写类的框架。传统的 JavaScript 使用构造函数来自定义对象,用 prototype 来实现继承。在 Modello 中,你可以忘掉晦涩的 prototype,因为 Modello 使用类来设计对象,用类来实现继承,就像其它面向对象语言一样,并且使用起来更加简单。不信吗?请继续往下看。

  
  
使用 Modello 编写的类所具备如下特性:  
  
私有成员、公共成员和静态成员  
类的继承,多继承  
命名空间  
类型鉴别  
Modello 还具有以下特性:  
  
更少的概念,更方便的使用方法  
小巧,只有两百行左右的代码  
设计期和运行期彻底分离,使用继承的时候不需要使用 prototype,也不需要先创建父类的实例  
兼容 prototype.js 的类,兼容 JavaScript 构造函数  
跨浏览器,跨浏览器版本  
开放源代码,BSD licenced,允许免费使用在个人项目或者商业项目中  
下面介绍 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) {  
return x + 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 = new Point(1, 2);  
point = Point.create(1, 2);  
point = Class.get("Modello.Point").create(1, 2);  
zpoint = new ZPoint(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 的完整参考说明和下载地址:  
  
  

(责任编辑:admin)

上一篇:星鸿娱乐平台:getElementById().innerHTML与getElementB

下一篇:没有了

推荐内容

客户服务热线

400 888 8932

在线客服