使用Modello编写JavaScript类


Posted in Javascript onDecember 22, 2006

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

以上就是 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

Javascript 相关文章推荐
使用JavaScript检测Firefox浏览器是否启用了Firebug的代码
Dec 28 Javascript
javascript实现十秒钟后注册按钮可点击的方法
May 13 Javascript
实例讲解JavaScript中instanceof运算符的用法
Jun 08 Javascript
jquery制做精致的倒计时特效
Jun 13 Javascript
Vue 组件传值几种常用方法【总结】
May 28 Javascript
VUE实现可随意拖动的弹窗组件
Sep 25 Javascript
微信小程序使用二次贝塞尔曲线画波浪
Dec 25 Javascript
ionic4+angular7+cordova上传图片功能的实例代码
Jun 19 Javascript
vue实现表单录入小案例
Sep 27 Javascript
vue使用高德地图点击下钻上浮效果的实现思路
Oct 12 Javascript
vue addRoutes路由动态加载操作
Aug 04 Javascript
详解javascript脚本何时会被执行
Feb 05 Javascript
获取Javscript执行函数名称的方法
Dec 22 #Javascript
Javascript开发包大全整理
Dec 22 #Javascript
用js重建星际争霸
Dec 22 #Javascript
js版本A*寻路算法
Dec 22 #Javascript
优化JavaScript脚本的性能的几个注意事项
Dec 22 #Javascript
网页设计常用的一些技巧
Dec 22 #Javascript
用JavaScript脚本实现Web页面信息交互
Dec 21 #Javascript
You might like
array_multisort实现PHP多维数组排序示例讲解
2011/01/04 PHP
PHP冒泡算法详解(递归实现)
2014/11/10 PHP
帝国cms常用标签汇总
2015/07/06 PHP
WordPress特定文章对搜索引擎隐藏或只允许搜索引擎查看
2015/12/31 PHP
php有效防止图片盗用、盗链的两种方法
2016/11/01 PHP
使用Jquery Aajx访问WCF服务(GET、POST、PUT、DELETE)
2012/03/16 Javascript
javascript简单事件处理和with用法介绍
2013/09/16 Javascript
javascript 实现字符串反转的三种方法
2013/11/23 Javascript
jQuery实现表格隔行及滑动,点击时变色的方法【测试可用】
2016/08/20 Javascript
JavaScript 函数节流详解及方法总结
2017/02/09 Javascript
借助node实战JSONP跨域实例
2017/03/30 Javascript
JavaScript数组去重的方法总结【12种方法,号称史上最全】
2019/02/28 Javascript
[02:56]DOTA2亚洲邀请赛 VG出场战队巡礼
2015/02/07 DOTA
[01:35]2018完美盛典章节片——共竞
2018/12/17 DOTA
python中的迭代和可迭代对象代码示例
2017/12/27 Python
Python使用matplotlib填充图形指定区域代码示例
2018/01/16 Python
python2.7读取文件夹下所有文件名称及内容的方法
2018/02/24 Python
详解Django rest_framework实现RESTful API
2018/05/24 Python
使用PyInstaller将python转成可执行文件exe笔记
2018/05/26 Python
python利用百度AI实现文字识别功能
2018/11/27 Python
python utc datetime转换为时间戳的方法
2019/01/15 Python
Python matplotlib绘制饼状图功能示例
2019/09/10 Python
python多进程间通信代码实例
2019/09/30 Python
Python线程指南分享
2019/11/19 Python
澳大利亚优质葡萄酒专家:Vintage Cellars
2019/01/08 全球购物
Java servlet面试题
2012/03/04 面试题
学生励志演讲稿
2014/01/06 职场文书
初中生评语大全
2014/04/24 职场文书
公司晚会策划方案
2014/05/17 职场文书
日语专业毕业生自荐书
2014/06/18 职场文书
教师党员个人整改措施
2014/10/27 职场文书
乡镇一岗双责责任书
2015/01/29 职场文书
2015年思想品德教学工作总结
2015/07/22 职场文书
假如给我三天光明:舟逆水而行,人遇挫而达 
2019/10/29 职场文书
Vue CLI中模式与环境变量的深入详解
2021/05/30 Vue.js
redis的list数据类型相关命令介绍及使用
2022/01/18 Redis