Ext JS 4 类体系(Class System)

Ext JS在其历史上第一次进行了彻底的类体系的重构。新的架构应用到了几乎每一个Ext JS 4.x的类中,因此在使用它编写代码之前理解它是非常必要的。

本手册适用于所有在Ext JS 4.x中创建新类和继承已有类的程序员。分为四部分:

  • 第I部分:“Overview”分析了鲁棒的类体系的需求
  • 第II部分:“Naming Conventions”讨论命名类、方法、属性、变量和文件的最佳实践
  • 第III部分:“Hands-on”给出了代码示例
  • 第IV部分:“Errors Handling & Debugging”给出了一些处理异常的技巧

I. Overview

Ext JS 4包含超过300个类。目前Ext JS社区聚集了来自世界各地各种编程背景的二十多万名开发人员。对于这种规模的框架,我们在提供一般的编码架构方面面临巨大挑战:

  • 便于学习
  • 快速开发,便于调试,便于部署
  • 条理清晰,高可扩展性,高可维护性

JavaScript是一种无类型,面向原型(prototype-oriented)语言。因此,JavaScript本质上最强大的特性之一就是灵活性。它可以通过多种方式、不同编码风格和技术完成同一任务。但是高灵活性是以不可预测性为代价的。如果没有统一的结构,JavaScript代码是难以理解、维护和重用的。

另一方面,基于类(class-based)的编程仍然是OOP最常见的模型。基于类的语言(class-based languages)通常是强类型的(strong-typing),支持封装,且有标准的编码规范。通过使程序员们遵循一系列的规则,代码变得更加可预测、可扩展。然而,它们没有JavaScript等语言的动态性。

每种方式都有各自的优点和缺点。我们是否能够只取它们的优点,隐藏它们的缺点呢?答案是肯定的,我们在Ext JS 4中实现了这种解决方案。

II. Naming Conventions

代码中的classes、namespaces和文件名使用一致的命名规范提高了代码的条理性、结构性和可读性。

1) Classes

类名只能含有字母和数字字符。多数情况下应该避免使用数字,除非这些数字属于某个专业术语。不要使用下划线、连字符和任何其他的非字母数字的字符。例如:

  • MyCompany.useful_util.Debug_Toolbar是不推荐使用的
  • MyCompany.util.Base64是可接受的

类名应该使用点符号(.)分组到相应的包中。至少应该有一个唯一的顶层命名空间。例如:

MyCompany.data.CoolProxy
MyCompany.Application

顶层命名空间和类名应该使用驼峰命名法,其他的应该全部使用小写。例如:

MyCompany.form.action.AutoLoad

非Sencha发布的类不应该使用Ext作为顶层命名空间。

缩写词也应该遵循驼峰命名法。例如:

  • 使用Ext.data.JsonProxy代替Ext.data.JSONProxy
  • 使用MyCompany.util.HtmlParser代替MyCompary.parser.HTMLParser
  • 使用MyCompany.server.Http代替MyCompany.server.HTTP

2) Source Files

类名直接映射到该类存储的路径。因此,每个类必然只对应一个文件。例如:

  • Ext.util.Observable存储在path/to/src/Ext/util/Observable.js
  • Ext.form.action.Submit存储在path/to/src/Ext/form/action/Submit.js
  • MyCompany.chart.axis.Numeric存储在path/to/src/MyCompany/chart/axis/Numeric.js

path/to/src是你的应用程序的类所在目录。所有的类都应该存储在这个根目录中,而且应该分为适当的包以便于开发、维护和部署。

3) Methods and Variables

  • 类似于类名,方法名和变量名也只能使用字母数字(alphanumeric)字符。多数情况下,应该避免使用数字,除非数字属于某个专业术语。不要使用下划线、连字符和其他非字母数字(nonalphanumeric)字符。
  • 方法名和变量名也应该遵循驼峰命名法。这也适用于缩写词。
  • 例子
    • 合理的方法名:encodeUsingMd5();getHtml()代替getHTML();getJsonResponse()代替getJSONResponse() ;parseXmlContent()代替parseXMLContent()
    • 合理的变量名:isGoodName、base64Encoder、xmlReader、httpServer

4) Properties

  • 类变量(class property)命名遵循与方法名变量名相同的规范,除了静态常量(static constant)的情况。
  • 静态类常量应该全部采用大写字符。例如:
    • Ext.MessageBox.YES = "Yes"
    • Ext.MessageBox.NO = "No"
    • MyCompany.alien.Math.PI = "4.13"

III. Hands-on(动手实践)

1. 声明(Declaration)

1.1) 老方法(The Old Way)

如果你用过之前版本的Ext,那么你可能对使用Ext.extend创建类比较熟悉:

var MyWindow = Ext.extend(Object, { ... });

这种方法便于创建一个继承自其他类的新类。除了直接继承,没有支持类创建其他方面的API,如configuration、statics和mixins。我们下面简短地回顾一下这些项目。

我们看一个例子:

My.cool.Window = Ext.extend(Ext.Window, { ... });

在这个例子中,我们想要namespace一个新类,使它继承自Ext.Window。需要注意两点:

1.赋值之前,My.cool必须是一个存在的对象

2.在引用Ext.Window之前,它必须加载到页面上

第一项通常采用Ext.namespace(即Ext.ns)来解决。该方法递归地遍历对象/属性树(object/Property tree),并在不存在的情况下创建它。烦人的是,你需要在Ext.extend之前加上它们。

Ext.ns('My.cool');
My.cool.Window = Ext.extend(Ext.Window, { ... });

第二个问题却不易解决,因为Ext.Window可能依赖于许多其他的类,它直接或间接地继承自这些类,同样的,这些被依赖的类可能又依赖更多的其他的类。鉴于此,采用Ext JS 4之前版本写的程序通常include整个库(ext-all.js),即使仅仅用到了很少一部分。

1.2)新方法(The New Way)

Ext JS 4消除了上述缺点,创建类时只要记住Ext.define一个方法即可。它的基本语法如下:

Ext.define(className, members, onClassCreated);

  • className:类名
  • members是以key-value对格式描述的类成员集合
  • onClassCreated是一个可选的回调函数(callback),当该类的所有依赖准备完毕且类创建完成时调用。由于创建类的新的异步特质(new asynchronous nature),这个回调函数在很多情况下有用。这将在Section IV中进一步讨论。

示例:

Ext.define('My.sample.Person', {
    name: 'Unknown',

    constructor: function(name) {
        if (name) {
            this.name = name;
        }

        return this;
    },

    eat: function(foodType) {
        alert(this.name + " is eating: " + foodType);

        return this;
    }
});

var aaron = Ext.create('My.sample.Person', 'Aaron');
    aaron.eat("Salad"); // alert("Aaron is eating: Salad");

我们使用Ext.create()方法创建了一个My.sample.Person的实例。我们可以使用new创建(new My.sample.Person())。但是我们推荐使用Ext.create,因为它允许你使用动态载入(dynamic loading)。更多关于dynamic loading的信息请参考 Get Started Guide。

2.Configuration

在Ext JS 4中,我们引入了专用的config属性,使得类在创建之前使用强大的Ext.class预处理器处理。包含以下特征:

  • Configurations完全是从其他类成员封装而来
  • 如果类中没有定义,每个config属性的Getter和setter方法自动生成到类的prototype。
  • 每个config属性会生成一个apply方法。自动生成的setter方法在设置值之前调用apply方法。如果你要在设置值(setting the value)之前执行定制的逻辑,可以复写(override)一个config属性的apply方法。如果apply没有返回值,则setter将不会设置值。例如下面的applyTitle:

这里是一个例子:

Ext.define('My.own.Window', {
   /** @readonly */
    isWindow: true,

    config: {
        title: 'Title Here',

        bottomBar: {
            enabled: true,
            height: 50,
            resizable: false
        }
    },

    constructor: function(config) {
        this.initConfig(config);

        return this;
    },

    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 && bottomBar.enabled) {
            if (!this.bottomBar) {
                return Ext.create('My.own.WindowBottomBar', bottomBar);
            }
            else {
                this.bottomBar.setConfig(bottomBar);
            }
        }
    }
});

这里是一个使用上述定义的例子:

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 }); // Bottom bar's height is changed to 100

3.statics

static成员可以使用statics config来定义:

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 ++;

        return this;
    }
});

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"

IV. Errors Handling & Debugging

Ext JS 4提供了一些帮助调试和错误处理的特性。

  • 可以使用Ext.getDisplayName()来获取任意方法的显示名(display name)。例如:

 throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');

  • 若使用基于WebKit浏览器(Chrome或Safari),用Ext.define()定义的类的方法抛出错误时,可以在调用栈(call stack)中看到方法名和类名。例如,下面是Chrome里的示例:


See Also

  • Dynamic Loading and the New Class System
  • Classes in Ext JS 4: Under the Hood
  • The Class Definition Pipeline


英文原文链接:The Class System

你可能感兴趣的:(JavaScript,js,ext,ext,Class)