视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
使用Modello编写JavaScript类_javascript技巧
2020-11-27 20:34:06 责编:小采
文档

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 = 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
// 上面的类均可替换成已注册的类名 下载本文

显示全文
专题