《Ext JS权威指南》——2.7节Ext JS 4语法

    xiaoxiao2022-05-27  191

    2.7 Ext JS 4语法1.配置对象Ext JS的基本语法就是使用树状的配置对象来定义界面,其格式如下:

    { config_options1:value1, config_options1:value2, … config_optionsn:valuen, layout:{}, items:[ {},//配置对象 {}//配置对象 …   ], listeners:{ //定义事件(根据需要而定) click:function(){}, dblclick:function(){} … } }

    格式中从config_options1、config_options2到config_optionsn都是API文档中对象的配置项(config options)。很多初学者会错误地认为API不全,找不到配置项。事实上API是完整的,只是有些布局隐含了面板或其他部件,但都在同一配置对象内定义。例如在使用Accordion布局的时候,想消除布局标题栏右边的小图标,需要使用hideCollapseTool属性,而该属性是在Panel对象里的。在这方面,Ext JS 4的API已经做了调整了,增加了一个其他配置项(Other Configs)的列表。属性layout可以是对象,也可以是字符。该属性表示在当前容器内使用什么布局来填充子控件。如果没有特殊要求,直接使用布局的别名作为值,例如,2.3节的示例中Viewport使用了Fit布局来放置子控件。如果有特殊要求,则要使用对象来定义该值。例如,如果使用Hbox布局,布局内的子控件需要居中对齐,则定义如下:

    layout:{ type:'hbox', align:'middle' }

    属性items是一个数组,可以在里面定义当前控件的子控件,里面可以是1个或多个配置项,根据你的需要而定。例如2.3节的示例,在Viewport下使用了一个Panel面板作为其面板。属性listeners是一个对象,可以在里面绑定事件,对象的属性就是事件名称,属性值就是要执行的函数。2.关于xtype在使用Ext JS编写应用时,很多时候通过定义xtype来指定该位置使用什么组件,例如2.3节的示例中的“xtype:"panel"”,这里指定使用面板作为当前位置的组件。这样做的主要目的是简化代码。如果没有xtype,就得先使用变量指向一个组件,然后将其加入父组件中,或者直接在父组件的定义中加入一堆由new关键或Ext.Create方法创建的组件。这不但影响了书写代码的效率,还影响了代码的可读性。有xtype存在,就不用担心这些问题了。在某些组件下,会默认其内部组件为某些组件,因而也就不需要书写xtype语句。例如,在2.3节的示例中把xtype去掉,代码也能正常运行,因为面板是Viewport内默认的组件。一般来说,没特别声明,使用的都是Panel组件。定义xtype,一般使用组件的别名。可在API文档的组件说明文档的顶部或Component对象的说明中找到组件的xtype值。

    使用new关键字创建对象在Ext JS 4版本之前,一直使用new关键字创建对象,其语法如下:

    new classname([config])其中calssname是指类名;config是可选参数,为类的配置对象(config options),类型为JSON对象。在2.3节的示例中,Ext.Viewport就是使用该方法创建的。4.使用Ext.create方法创建对象Ext.create方法是新增的创建对象的方法,其语法如下:

    Ext.create(classname,[config])

    其中classname可以是类的全名、别名或备用名;config是可选参数,为类的配置对象(config options),类型为对象。将2.3节的示例中的以下代码:

    new Ext.Viewport({ 修改为: Ext.create('Ext.Viewport',{

    效果是一样的。那为什么要增加这样一个方法呢?我们来研究一下create方法的源代码。在ClassManger.js文件中,可以找到以下代码:

    Ext.apply(Ext, { create: alias(Manager, 'instantiate'), ... }) 从代码可知create方法其实是Ext.ClassManager类的instantiate方法的别名,其代码如下: instantiate: function() { var name = arguments[0], nameType = typeof name, args = arraySlice.call(arguments, 1), alias = name, possibleName, cls; if (nameType != 'function') { if (nameType != 'string' && args.length === 0) { args = [name]; name = name.xclass; } //省略调试代码 cls = this.get(name); } else { cls = name; } if (!cls) { possibleName = this.getNameByAlias(name); if (possibleName) { name = possibleName; cls = this.get(name); } } if (!cls) { possibleName = this.getNameByAlternate(name); if (possibleName) { name = possibleName; cls = this.get(name); } } if (!cls) { //省略调试代码 Ext.syncRequire(name); cls = this.get(name); } //省略调试代码 return this.getInstantiator(args.length)(cls, args); },

    首先将name变量指向从参数中获取的类名,然后判断name是不是函数,如果不是,且其不是字符串,则从xclass属性中获取类名。接着使用get方法获取对象,并将cls变量指向对象。get方法的源代码如下:

    get: function(name) { var classes = this.classes; if (classes[name]) { return classes[name]; } var root = global, parts = this.parseNamespace(name), part, i, ln; for (i = 0, ln = parts.length; i < ln; i++) { part = parts[i]; if (typeof part != "string") { root = part; } else { if (!root || !root[part]) { return null; } root = root[part]; } } return root; },

    代码中的classes对象包括了Ext JS的所有类,因而代码首先根据name从classes中寻找类,如果存在,则返回对象;如果不存在,则说明是用户自定义的类,需要从Ext.global中寻找。查找工作首先要做的是使用parseNamespace方法拆解类名,其代码如下:

    parseNamespace: function(namespace) { //省略调试代码 var cache = this.namespaceParseCache; if (this.enableNamespaceParseCache) { if (cache.hasOwnProperty(namespace)) { return cache[namespace]; } } var parts = [], rewrites = this.namespaceRewrites, root = global, rewrite, from, to, i, ln; for (i = 0, ln = rewrites.length; i < ln; i++) { rewrite = rewrites[i]; from = rewrite.from; to = rewrite.to; if (namespace === from || namespace.substring(0, from.length) === from) { namespace = namespace.substring(from.length); if (typeof to !== "string") { root = to; } else { parts = parts.concat(to.split(".")); } break; } } parts.push(root); parts = parts.concat(namespace.split(".")); if (this.enableNamespaceParseCache) { cache[namespace] = parts; } return parts; },

    代码中namespaceParseCache对象的作用是使用类名作为关键字并指向拆解后的类名数组,这样,当该类被多次使用时,就可以直接从namespaceParseCache对象中获取拆解的类名数组,而不需要再拆解一次,从而加快运行速度。通过enableNamespaceParseCache属性可配置是否开启namespaceParseCache对象的缓存功能,默认是开启的。如果在enableNamespaceParseCache中已存在类名的拆解结果,则返回结果。属性namespaceRewrites的定义如下:

    namespaceRewrites: [{ from: 'Ext.', to: Ext }],

    在其他文件中找不到为namespaceRewrites添加数据的代码,因而在循环中,如果类名是以“Ext.”开头的,root会指向Ext对象;如果不是,root就是初始值,指向Ext.global。接着,将root指向的对象存入parts数组,再将拆分类名产生的数组与parts数组合并。如果开启了缓存功能,在namespaceParseCache对象中,将以类名为关键字指向parts数组,最后返回parts数组。数组返回后,通过循环的方式来查找类定义。因为返回数组(parts)的第1个数据不是Ext对象就是Ext.golbal对象,因而变量root在第一次循环时,会指向Ext对象或Ext.golbal对象。在后续循环中,会根据拆分的类名在Ext或Ext.gdbal对象中逐层找下去。如果期间“root[part]”不是对象,说明不存在该类,返回null。如果找到了,返回root指向的对象。如果cls不是指向对象,则尝试使用别名方法查找对象。查找时,首先使用getName-ByAlias方法将别名转换为类名,其代码如下:

    getNameByAlias: function(alias) { return this.maps.aliasToName[alias] || ''; }, 在maps属性中保存了以下3个对象: maps: { alternateToName: {}, aliasToName: {}, nameToAliases: {} },

    简单来说,maps中的对象就是一个对照表,通过它就可轻松地找到类名和别名。对象alternateToName的作用是通过备用名获得类名,它以备用名作为关键字、类名作为值;对象aliasToName的作用是通过别名获取类名,它以别名作为关键字、类名作为值;对象nameToAliases的作用是通过类名获取别名,它以类名作为关键字、别名作为值。在创建类的时候,会把类名、别名和备用名写入对照表。如果getNameByAlias方法返回的不是空字符串,说明变量name保存的是别名,需将name修改为类名,然后通过get方法获取对象。如果使用别名也找不到对象,则可尝试使用备用名来查找对象,执行代码与使用别名查找的方式类似。如果还没有找到,则可尝试使用syncRequire方法下载对象,其代码如下:

    syncRequire: function() { this.syncModeEnabled = true; this.require.apply(this, arguments); this.refreshQueue(); this.syncModeEnabled = false; },

    在上面的代码中,syncModeEnabled方法可控制Loader对象的require方法通过同步的方式去下载对象。如果实在是找不到,就会抛出异常。最后使用getInstantiator方法实例化对象,其代码如下:

    getInstantiator: function(length) { if (!this.instantiators[length]) { var i = length, args = []; for (i = 0; i < length; i++) { args.push('a['+i+']'); } this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')'); } return this.instantiators[length]; },

    代码中的数组instantiators起缓存作用,因为同样的参数长度产生的匿名函数是一样,没必要每次都重新创建一次。最后返回的匿名函数如下:(function anonymous(c, a) {return new c(a[0], a[1], … ,a[n]);})代码中的n就是参数的长度,例如length的值为5,则匿名函数如下:(function anonymous(c, a) {return new c(a[0], a[1], a[2], a[3], a[4]);})匿名函数返回后会立即执行,参数c指向对象的cls,a指向实例化对象时的参数。也就是说,对象是在匿名函数中实例化的,这样可以保证对象的作用域是安全的。从以上的分析可以了解到,创建对象的新方法不但可以实现动态加载,而且可以保证作用域的安全,应该优先使用。5.使用Ext.widget或Ext.createWidget创建对象Ext.widget的作用是使用别名来创建对象,其语法与2.7.2节介绍的Ext.create是一样的,只是classname使用的是对象的别名。Ext.createWidget是Ext.widget的另外一种使用方法而已,在源代码中的定义如下:

    Ext.createWidget = Ext.widget; Ext.widget的代码如下: widget: function(name) { var args = slice.call(arguments); args[0] = 'widget.' + name; return Manager.instantiateByAlias.apply(Manager, args); }, 也就是说,它使用ClassManager对象的instantiateByAlias方法创建对象,其代码如下: instantiateByAlias: function() { var alias = arguments[0], args = slice.call(arguments), className = this.getNameByAlias(alias); if (!className) { className = this.maps.aliasToName[alias]; //省略调试代码 Ext.syncRequire(className); } args[0] = className; return this.instantiate.apply(this, args); },

    代码先是根据别名寻找类名,如果找不到就抛出异常或尝试使用syncRequire方法加载类文件,最后调用instantiate方法创建对象。6.使用Ext.ns 或 Ext.namespace定义命名空间在使用C#或Java做开发时,很多时候都会使用命名空间来组织相关类和其他类型。在JavaScript中并没有提供这样的方式,不过可以通过定义一个全局对象的方式来实现,譬如你要定义一个“MyApp”的命名空间,你可以这样:

    MyApp ={};

    这里要注意,不要使用var语句去定义全局对象。在Ext JS中,使用Ext.namespace方法可创建命名空间,其语法如下:Ext.namespace(namespace1,namespace2,…,namespacen)其中namespace1、namespace2和namespacen都是字符串数据,是命名空间的名字,例如:

    //推荐用法 Ext.namespace("MyApp","MyApp.data","MyApp.user"); Ext.ns("MyApp","MyApp.data","MyApp.user"); //或者,不建议使用 Ext.namespace(,"MyApp.data","MyApp.user"); Ext.ns("MyApp.data","MyApp.user");

    Ext.ns只是Ext.namespace的简单写法。在ClassManager.js中可找到Ext.namespace的createNamespace方法的实现代码:

    createNamespaces: function() { var root = global, parts, part, i, j, ln, subLn; for (i = 0, ln = arguments.length; i < ln; i++) { parts = this.parseNamespace(arguments[i]); for (j = 0, subLn = parts.length; j < subLn; j++) { part = parts[j]; if (typeof part !== 'string') { root = part; } else { if (!root[part]) { root[part] = {}; } root = root[part]; } } } return root; },

    从上面代码可以看到,Ext.namespace创建的命名空间是保存在global对象里的。注意循环结构,数组长度都是先保存到一个局部变量再使用的,原因是JavaScript与C#、Java等语言不同,每次循环都要计算一次数组长度,这样会降低性能。在第一个循环下,使用了parseNamespace方法拆分命名空间的字符串。在Ext JS初始化时,this指向的是window对象,因而global指向的是window对象。数组返回到createNamespace方法后,开始执行完循环,最后在window对象下生成了以下结构的全局对象:

    {   MyApp:{    Data:{}   } }

    虽然生成的是全局对象,但是在作用域链上,它可以直接在Ext JS的作用域链内搜索对象,而不需要到最外层的全局作用域链搜索对象,这是最大的不同。在任何编程语言里都不提倡使用全局变量,尤其是在JavaScript里,全局对象在每一层的作用域链里都搜索不到时,才会在全局作用域链里搜索,效率相当低。因此,在JavaScript里不仅不推荐使用全局变量,而且建议当有一个变量不是在当前作用域定义的,并要多次使用时,建议将该变量保存在局部变量中,使用局部变量来进行操作,以避免在域链中多次搜索对象而降低性能。因此,建议的方法是在Ext对象下创建命名空间,譬如创建以下的命名空间:

    Ext.ns("Ext.MyApp","Ext.MyApp.data","Ext.MyApp.user");

    最好的方法是使用Ext已定义的命名空间“Ext.app”,如果是扩展或插件则使用“Ext.ux”。7.使用Ext.define定义新类在Ext JS 3中,定义新类使用的是Ext.extend方法,因为Ext JS 4的类系统进行了重新架构,所以定义新类的方法也修改为了Ext.define方法,其语法如下:

    Ext.define(classname,properties,callback);

    其中:classname:要定义的新类的类名。properties:新类的配置对象,对象里包含了类的属性集对象,表2-1列出了常用的属性及其说明。callback:回调函数,当类创建完后执行该函数。

    代码中,initConfig方法执行后就会为congfig的属性创建set和get方法,这样创建类的示例后,就可以直接通过set和get方法读写属性的值,譬如例子中用setWidth设置了配置中width的值,使用getHeight获取height的值

     定义静态方法,例如: Ext.define("subclass",{   statics:{    method:function(args){    return new this(args);     } statics   }, constructor:function(config){    this.initConfig(config);    …   },   … }); var myclass=subclass.method("class");

    类的创建过程将在第4章讲解,下面我们来实践一下。在Firefox中打开2.3节的示例,然后打开Firebug,在控制台中输入以下代码:

    Ext.define("Calculator",{ constructor:function(){ return this; }, plus:function(v1,v2){ return v1+v2; }, minus:function(v1,v2){ return v1-v2; }, multiply:function(v1,v2){ return v1*v2; }, divid:function(v1,v2){ return v1/v2 } }); var cal=new Calculator(); console.log(cal.plus(87,28)); //115 console.log(cal.minus(87,28)); //59 console.log(cal.multiply(7,8)); //56 console.log(cal.divid(28,7)); //4

    代码中定义了一个名称为“Calculator”的类,它包含加、减、乘、除4个方法。运行后,将Firebug标签页切换到DOM标签,可看到如图2-2所示的Calculator类。

    继续创建一个继承自Calculator类的新类NewCalculator,新类添加了十进制转换为十六进制的方法,代码如下:

    Ext.define('NewCalculator',{ extend:'Calculator', hex:function(v1){ return v1.toString(16); } }); var ncal=new NewCalculator(); console.log(ncal.hex(28)); //1c

    代码中extend属性的值为Calculator,表示NewCalculator将继承自Calculator类。方法hex是新增的进制转换方法。运行后可在DOM树中看到如图2-3所示的NewCalculator类。

    比较一下图2-3与图2-2,可看到Calculator类的超类(superclass)是Ext.Base,而NewCalculator类的超类是Calculator,这说明,没有使用extend属性定义的类会默认从Ext.Base中继承。

    继续我们的实践,这次要做的是将HEX、BIN、OCT这3个实现不同进制转换的类混合到NewCalculator类中。第一步要做的是定义3个实现进制转换的类:

    Ext.define('HEX',{ hex:function(v1){ return v1.toString(16); } }); Ext.define('BIN',{ bin:function(v1){ return v1.toString(2); } }); Ext.define('OCT',{ oct:function(v1){ return v1.toString(8); } }); 然后将HEX、BIN和OCT三个功能混合到继承自Calculator类的NewCalculator类中,这样NewCalculator除了实现加减乘除功能外,还能实现十进制到二进制、八进制和十六进制的转换,代码如下: Ext.define('NewCalculator',{ extend:'Calculator', mixins:{ Hex:'HEX', Bin:'BIN', Oct:'OCT' }, convert:function(value,type){ switch(type){ case 2: return this.bin(value) break; case 8: return this.oct(value) break; default: return this.mixins.Hex.hex.call(this,value); break; } } }); var ncal=new NewCalculator(); console.log(ncal.convert(25,2)); //11001 console.log(ncal.convert(25,8)); //31 console.log(ncal.convert(25,16)); //19

    在代码中,使用convert方法可进行进制转换,第2个参数type表示要转换的进制类型。调用mixins属性定义的混合功能有两种方法:第一种是二进制和八进制中使用的直接调用的方法,第二种是十六进制中使用call方法调用的方法。运行后,在DOM中可看到如图2-4所示的NewCalculator类。

    在图2-4中的原型(prototype)节点里,可看到mixins对象中的3个属性都指向了对应的BIN、HEX和OCT对象,而这三个对象中的方法也直接附加到原型上了,这也就解释了为什么可以使用两种方法调用混合功能的方法。要注意的是,如果存在同名方法,譬如将BIN、HEX和OCT定义的方法都修改为convert,那么使用直接调用的方法就会出现错误,因为在JavaScript中,存在同名函数,前面的定义会被最后定义的函数覆盖,所以在不确定是否有同名方法的情况下,建议还是使用第二种call的方法。定义3个进制转换类实在麻烦,在一个Convert类中,预设好一个type参数,然后根据type定义的类型进行转换就方便多了,代码如下:

    Ext.define("Convert",{ config:{ type:"hex" }, type_num:16, constructor:function(config){ this.initConfig(config); return this; }, applyType:function(type){ this.type_num= type=="hex" ? 16 : ( type=="oct" ? 8 : 2); return type; }, convert:function(v){ return v.toString(this.type_num); } }); var cv=new Convert(); console.log(cv.convert(29)); //1d cv.setType("oct"); console.log(cv.convert(29)); //35 cv.setType("bin"); console.log(cv.convert(29)); //11101

    在Convert类中,type属性在initConfig执行后会自动生成applyType、setType、getType和resetType这4个方法。当type的值发生改变时,会触发applyType方法,因而可以重写applyType方法以实现所需的功能。在applyType方法中,根据type的值修改了type_num的值,这样在convert方法中就可以使用type_num值直接进行进制转换了。代码运行后在DOM中可看到如图2-5所示的Convert类。

    在图2-5的原型中,可看到与Type有关的4个方法。实现这么简单的功能,每次都要使用new关键字对象太麻烦了。如果是静态类就好了,所以修改代码如下:

    Ext.define("Convert",{ statics:{ hex:function(v){ return v.toString(16); }, oct:function(v){ return v.toString(8); }, bin:function(v){ return v.toString(2); } }, constructor:function(config){ return this; } }); console.log(Convert.hex(29)); //1d console.log(Convert.oct(29)); //35 console.log(Convert.bin(29)); //11101

    在Convert类中,3个进制转换方法都定义在statics属性中。运行后在DOM树中可看到如图2-6所示的Convert类。

    在图2-6中,3个进制转换方法不在原型里,而是直接挂在类节点下。现在大家应该清楚静态类和其他类的区别了。

    相关资源:七夕情人节表白HTML源码(两款)

    最新回复(0)