ExtJS 史上第一次进行了重整新的类系统的大重构。新的架构以ExtJS 4.X所编写的每一个类作为后盾,因此在你编写代码以前理解它是非常重要的。
这个手册主要面向任何想在ExtJS 4.x中新建或者扩展类的开发人员。它分成四个部分:
Ø 部分一:“综观”解释了稳定的类系统的需求
Ø 部分二:“命名规则”讨论给类、方法、属性、变量和文件命名的最佳实践
Ø 部分三:“动手实践”提供详细的一步步编码的例子
Ø 部分四:“错误处理&调试”提供如何处理一场的小建议和小计谋
ExtJS 4 靠超过300 多个类驱动。我们拥有一个超过20万来自世界各地,具备各种编程背景的开发人员组成的巨大社区。在一个框架的范围内,我们面对提供一个通用的编码结构的那些大挑战:
Ø 简单易上手
Ø 开发快速、调试简单、部署无忧
Ø 结构良好,可扩展可维护
JavaScript 是 classless 的面向原型的语言。天性使然,灵活是这个语言最强大的特性。使用不同的方式,不同的编码形式和技术,都可以让工作有效。然而就是那个特性,带来了不可预知的代价。没有一个统一的形式,JavaScript代码可能很难去理解、维护和重用。
从另一方面来看,基于类的编程仍然是面向对象编程领域最受欢迎的模式。基于类的语言常常需要强类型,提供封装和标准的编码规范。一般而言要让开发人员遵守一大堆规则,而编码就会变得一直可预知、可扩展和规规矩矩。然而,他们不会有在JavaScript这样的语言中发现的同样的动态能力。
每种方法都有其利弊,但是我们是否可以利用两者好处的同时避免他们的坏处呢?答案是肯定的,我们在ExtJS 4中实现了这个解决方案。
至始至终为你编码的类、命名空间和文件名使用一致的命名规则有助于保持你代码的组织性、结构性和可读性。
类名应该只包含字母和数字字符。数字在大多数情况下是不鼓励使用的,除非他们属于一种技术手段。不要使用下划线,连字符或者其它任何非字母非数字的字符。举个例子:
Ø MyCompany.useful_util.Debug_Toolbar 不鼓励这样命名
Ø MyCompany.util.Base64 是可以被接受的
类名应该被组成成为包,在包中合适恰当的使用对象属性点记号(.)分出命名空间。至少,应该只有唯一的顶层命名空间后面跟类名。举个例子:
MyCompany.data.CoolProxy
MyCompany.Application
顶层命名空间和真实类的命名应该采用Camel形式(单词的首字母都大写),其它所有事物都应该是小写的。举个例子:
MyCompany.form.action.AutoLoad
不是Sencha发行的类永远不应该使用Ext作为顶层命名空间的名字。
首字母缩略词也应该遵守上面列出的Camel形似命名规则。示例如下:
Ext.data.JsonProxy 而不是Ext.data.JSONProxy
MyCompany.util.HtmlParser 而不是 MyCompary.parser.HTMLParser
MyCompany.server.Http 而不是MyCompany.server.HTTP
类地址的名字应该直接指向文件被存储的路径。基于此,每个文件中只能有一个类,示例如下:
Ext.util.Observable 被存储在路径 /to/src/Ext/util/Observable.js 中
Ext.form.action.Submit 被存储在路径 /to/src/Ext/form/action/Submit.js中
MyCompany.chart.axis.Numeric 被存储在路径 /to/src/MyCompany/chart/axis/Numeric.js中
Path/to/src 是你的应用程序类所在的路径。所有的类都应该在这个公共的根下面,并且为了获得最好的开发、维护和部署体验,适当的赋予命名空间。
2)方法和变量
跟类名类似,方法和变量的名字应该只包含数字和字母字符。数字被允许的,但在大多数情况下是不被鼓励的,除非是属于某种技术手段。不要使用下划线、连字符,或者任何其它非字母和数字的字符。
方法和变量名字应该一直使用camel形式(第一个单词首字母小写,接下来的单词首字母都大写)。这也同样适用于首字母缩略词。
示例:
可以被接受的方法名字:encodeUsingMd5() getHtml() instead of getHTML() getJsonResponse() 而不是:getJSONResponse() parseXmlContent() instead ofparseXMLContent()
可以被接受的变量名称: var isGoodName var base64Encoder var xmlReader var httpServer
类属性的命名规则同上述方法和变量的命名规则是完全一样的,除开它们是静态常量的情况。
静态常量类属性应该都是大写的。示例如下:
Ext.MessageBox.YES = "Yes"
Ext.MessageBox.NO = "No"
MyCompany.alien.Math.PI = "4.13"
1.1)老方法
如果你使用过任何ExtJS的老版本,你一定熟悉使用 Ext.extend 去创建一个类:
var MyWindow = Ext.extend(Object, { ... });
这种方法很容易用来去创建一个继承另外一个类的新类。然而不像其它方式的继承,我们没有流畅的创建类的其它方面的API,比如配置,静态性和混合。我们很快将会重新详细的了解这些东西。
让我们看一下另外一个例子:
My.cool.Window = Ext.extend(Ext.Window, { ... });
在这个例子中我们给新类带上了命名空间,并且让它继承了 Ext.Window。这里我们需要专注于两个概念:
1. My.cool 需要在我们把 Window 当作它的属性前,是一个存在的对象
2. Ext.Window 在它能被引用前需要存在或者已经被加载进来
第一条常常使用 Ext.namespace(用 Ext.ns作别名)来解决。 这个方法横向递归对象/属性树,并在他们还不存在时创建之。 烦人的地方在于,你需要一直牢记把它们加在 Ext.extend 之上。
Ext.ns('My.cool');
My.cool.Window = Ext.extend(Ext.Window, { ... });
第二条专注起来不是很容易,因为 Ext.window 也许依赖于其它直接或间接继承的类,并且这样轮转下去,这些依赖也依赖于其它类的存在。由于那个原因,编写应用程序之前ExtJS 4 常常要包含包括了整个库的 ext-all.js ,即使他们也许仅仅只需要这个框架的一小部分。
1.2)新方法
ExtJS 4 仅仅使用一个方法排除了所有那些缺点。你仅需要记住如何创建类: Ext.define。它的简单语法是像下面这样:
Ext.define(className, members, onClassCreated);
className:类的名字
Members 代表一个类成员的键值对集合的对象
onClassCreated 是一个在类的所有依赖都已经准备好时回掉执行的可选方法,同时这个类本身完全创建了。归功于新的创建类的异步形式,这种会掉在很多情况下都会很有用。这将在第四部分讨论到。
示例:
Ext.define('My.sample.Person', {
name: 'Unknown',
constructor: function(name) {
if (name) {
this.name = name;
}
},
eat: function(foodType) {
alert(this.name + " is eating: " + foodType);
}
});
var aaron = Ext.create('My.sample.Person', 'Aaron');
aaron.eat("Salad"); // alert("Aaron is eating: Salad");
注意我们用 Ext.create() 方法创建了一个 My.sample.Person 的新实体。我们可能使用新的关键字(My.name.Person())。然而建议你形成一直使用 Ext.create 的习惯,因为它允许你利用动态加载的好处。更多关于动态加载的内容见入门指南。
在 ExtJS 4种,我们推出专注的 config 属性,它在类被创建以前通过强大的 Ext.Class 前处理器获得处理。特性包括:
对于其它类成员来说配置是完全封装的。
每一个配置属性的获取和设置方法将在类创建期间自动生成到类的原型中,如果这些方法还没有被定义。
一个apply方法也为每一个配置属性生成了。自动生成的设置器方法在设置值之前内部调用apply方法。如果你需要在设值之前运行一些定制逻辑,重写config属性的apply方法,如果apply没有返回值,那么设置器将不会设值。示例见下面的 applyTitle.
这是一个示例:
Ext.define('My.own.Window', {
/** @readonly */
isWindow: true,
config: {
title: 'Title Here',
bottomBar: {
height: 50,
resizable: false
}
},
constructor: function(config) {
this.initConfig(config);
},
applyTitle: function(title) {
if (!Ext.isString(title) || title.length === 0) {
alert('Error: Title must be a valid non-empty string');
}
else {
return title;
}
},
applyBottomBar: function(bottomBar) {
if (bottomBar) {
if (!this.bottomBar) {
return Ext.create('My.own.WindowBottomBar', bottomBar);
}
else {
this.bottomBar.setConfig(bottomBar);
}
}
}
});
/** A child component to complete the example. */
Ext.define('My.own.WindowBottomBar', {
config: {
height: undefined,
resizable: true
}
});
而下面是一个它是如何被使用的例子:
var myWindow = Ext.create('My.own.Window', {
title: 'Hello World',
bottomBar: {
height: 60
}
});
alert(myWindow.getTitle()); // alerts "Hello World"
myWindow.setTitle('Something New');
alert(myWindow.getTitle()); // alerts "Something New"
myWindow.setTitle(null); // alerts "Error: Title must be a valid non-empty string"
myWindow.setBottomBar({ height: 100 });
alert(myWindow.getBottomBar().getHeight()); // alerts 100
静态成员可以使用 statics 配置被定义。
Ext.define('Computer', {
statics: {
instanceCount: 0,
factory: function(brand) {
// 'this' in static methods refer to the class itself
return new this({brand: brand});
}
},
config: {
brand: null
},
constructor: function(config) {
this.initConfig(config);
// the 'self' property of an instance refers to its class
this.self.instanceCount ++;
}
});
var dellComputer = Computer.factory('Dell');
var appleComputer = Computer.factory('Mac');
alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac"
alert(Computer.instanceCount); // Alerts "2"
ExtJS 4 包含一些能在调试和错误处理工作上帮助你的有用特性。
你可以使用 Ext.getDisplayName() 去得到任何方法名字的显示。这在抛出错误时在错误描述信息中显示类名和方法名,特别有用。
throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');
如果一个错误在任何使用 Ext.define() 定义的类的任何方法中被抛出, 你应该使用一个基于 Webkit 内核的浏览器(Chrome 或者 Safari)去观察调用堆栈中方法和类的名字。例如,下面是它如何在Chrome中显示:
· Dynamic Loading and the New Class System
· Classes in Ext JS 4: Under the Hood
· The Class Definition Pipeline