使用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 相关文章推荐
jQuery实现列表自动循环滚动鼠标悬停时停止滚动
Sep 06 Javascript
Javascript执行效率全面总结
Nov 04 Javascript
鼠标左键单击冲突的问题解决方法(防止冒泡)
May 14 Javascript
JS中改变this指向的方法(call和apply、bind)
Mar 26 Javascript
原生JavaScript实现Ajax的方法
Apr 07 Javascript
巧方法 JavaScript获取超链接的绝对URL地址
Jun 14 Javascript
javascript中使用未定义变量或值的情况分析
Jul 19 Javascript
jQuery Ajax实现跨域请求
Jan 21 Javascript
jQuery.Form上传文件操作
Feb 05 Javascript
利用Ionic2 + angular4实现一个地区选择组件
Jul 27 Javascript
详解利用 Express 托管静态文件的方法
Sep 18 Javascript
Vue实现多页签组件
Jan 14 Vue.js
获取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
生成php程序的php代码
2008/04/07 PHP
php连接与操作PostgreSQL数据库的方法
2014/12/25 PHP
js+php实现静态页面实时调用用户登陆状态的方法
2015/01/04 PHP
PHP结合jQuery插件ajaxFileUpload实现异步上传文件实例
2020/08/17 PHP
javascript中的几个运算符
2007/06/29 Javascript
理解JavaScript变量作用域更轻松
2009/10/25 Javascript
jQuery实现原理的模拟代码 -6 代码下载
2010/08/16 Javascript
jQuery图片轮播的具体实现
2013/09/11 Javascript
JS简单的图片放大缩小的两种方法
2013/11/11 Javascript
jquery教程限制文本框只能输入数字和小数点示例分享
2014/01/13 Javascript
js操作iframe父子窗体示例
2014/05/22 Javascript
jQuery中:first选择器用法实例
2014/12/30 Javascript
Javascript实现商品秒杀倒计时(时间与服务器时间同步)
2015/09/16 Javascript
jquery无限级联下拉菜单简单实例演示
2015/11/23 Javascript
微信小程序之小豆瓣图书实例
2016/11/30 Javascript
关于vue的语法规则检测报错问题的解决
2018/05/21 Javascript
利用Webpack实现小程序多项目管理的方法
2019/02/25 Javascript
HTML+JavaScript实现扫雷小游戏
2019/09/30 Javascript
微信小程序实现星级评价
2019/11/20 Javascript
javascript浅层克隆、深度克隆对比及实例解析
2020/02/09 Javascript
Python 连连看连接算法
2008/11/22 Python
K-近邻算法的python实现代码分享
2017/12/09 Python
Python使用 Beanstalkd 做异步任务处理的方法
2018/04/24 Python
对Python3 goto 语句的使用方法详解
2019/02/16 Python
Python中按值来获取指定的键
2019/03/04 Python
python检查目录文件权限并修改目录文件权限的操作
2020/03/11 Python
python 利用Pyinstaller打包Web项目
2020/10/23 Python
HTML5 Canvas绘制五星红旗
2016/05/04 HTML / CSS
英国领先的NHS批准的在线药店:Pharmacy2U
2017/01/06 全球购物
三年级音乐教学反思
2014/01/28 职场文书
做一个有道德的人演讲稿
2014/05/14 职场文书
篮球比赛拉拉队口号
2014/06/10 职场文书
竞选班干部演讲稿100字
2014/08/20 职场文书
财务会计求职信范文
2015/03/20 职场文书
浅谈spring boot使用thymeleaf版本的问题
2021/08/04 Java/Android
前端JavaScript大管家 package.json
2021/11/02 Javascript