ExtJS4.0的类系统是整个框架的基础且核心的架构设施,其它所有的功能扩展都是建立在类系统上的。在ExtJS4.0中,类系统相对以前的版本有大幅度的改变,在以前的版本中,定义一个新类是在一个已经存在的类如Object的基础上调用
var myClass = Ext.extend(Object, { ... });进行扩展而来,这样我们不能方便的给新建的类添加statics属性或方法及mixins等功能,且新建的类需要依赖Object等这些要继承的类,Object也会递归的依赖它的父类,如果这些类未创建就会出现错误,所以在以前的版本中干脆把整个ext-all.js在一开始就全部加载进来并创建整个框架中定义的所有类,这样即使你在一个应用中只使用ExtJS的一个类也要把整个ExtJS的类加载进来,大大将低了程序运行效率。在extjs4.0中通过使用
Ext.define(className, members, onClassCreated);来创建类解决了上面的问题。其中className是创建的类的名字,是String类型的;members是给要创建的类添加的配置信息,主要包括requires(要求使用的类),extend(要继承的类),mixins(向创建的类中要掺进其它类的信息),config(给创建的类配置属性方法等),statics(给类增加静态属性或方法),以及其它的一些属性方法等信息,这些配置信息在后面会详细说明;onClassCreated是类创建后的回调函数。
在ExtJS4.0中,与类创建相关的js文件主要有src/core/src/class中的Base.js,Class.js,ClassManager.js,Loader.js。其中Base.js定义了Base类,Base类是ExtJS4.0中所有通过Ext.define定义的类的直接或间接父类,Base类中定义了一些通用的实例与静态属性与方法;Class.js文件中定义了Class类,所有通过Ext.define定义的类都是Class的实例(其实是Class构造函数中的newClass类);ClassManager.js文件中定义了ClassManager单例,它主要负责对类的管理,如类的创建与类实例化等;Loader文件中定义了Loader单例,实现了ExtJS4.0中动态加载类的功能。
以ExtJS中的Component类为例,它的类创建代码如下:
Ext.define('Ext.Component', { /* Begin Definitions */ alias: ['widget.component', 'widget.box'], extend: 'Ext.AbstractComponent', requires: [ 'Ext.util.DelayedTask' ], uses: [ 'Ext.Layer', 'Ext.resizer.Resizer', 'Ext.util.ComponentDragger' ], mixins: { floating: 'Ext.util.Floating' }, statics: { // Collapse/expand directions DIRECTION_TOP: 'top', DIRECTION_RIGHT: 'right', DIRECTION_BOTTOM: 'bottom', DIRECTION_LEFT: 'left', VERTICAL_DIRECTION: /^(?:top|bottom)$/ }, resizeHandles: 'all' } );
一个类创建前与创建后分别会有前置处理器与后置处理器对类进行装饰以增强类的功能,下面一张图片对类的创建过程作了一个详细的说明:
类创建前会有loader,extend,mixins,config,statics对类增加配置信息,且这些preprocessor是按顺序进行的,postprscessor主要包括alias(为类设置别名)与legacy等。
Ext.define实际上是ClassManager中的create方法的别名,ClassManager是一个单例,里面主要定义了与类的创建(create)与实例化类(instantiate)相关的方法。所以
Ext.define(className, data, onClassCreated);实际上是调用了
ClassManager.create(className, data, onClassCreated);。在此方法内部,会先将className添加到data,之后会new一个Class并返回,所以可以说Ext.define出的类都是Class这个类的实例(其实new Class后返回的是其内部的newClass类),下面是裁剪后的代码:
create: function(className, data, createdFn) { var manager = this; data.$className = className; return new Class(data, function() { var postprocessorStack = data.postprocessors || manager.defaultPostprocessors, registeredPostprocessors = manager.postprocessors, index = 0, postprocessors = [], postprocessor, postprocessors, process, i, ln; delete data.postprocessors; for (i = 0, ln = postprocessorStack.length; i < ln; i++) { postprocessor = postprocessorStack[i]; if (typeof postprocessor === 'string') { postprocessor = registeredPostprocessors[postprocessor]; if (!postprocessor.always) { if (data[postprocessor.name] !== undefined) { postprocessors.push(postprocessor.fn); } } else { postprocessors.push(postprocessor.fn); } } else { postprocessors.push(postprocessor); } } process = function(clsName, cls, clsData) { postprocessor = postprocessors[index++]; if (!postprocessor) { manager.set(className, cls); Ext.Loader.historyPush(className); if (createdFn) { createdFn.call(cls, cls); } return; } if (postprocessor.call(this, clsName, cls, clsData, process) !== false) { process.apply(this, arguments); } }; process.call(manager, className, this, data); }); },
下面是Class的核心代码:
Ext.Class = Class = function(newClass, classData, onClassCreated) { if (typeof newClass !== 'function') { onClassCreated = classData; classData = newClass; newClass = function() { return this.constructor.apply(this, arguments); };//这才是最终创建的类 } if (!classData) { classData = {}; } //创建类时可以自定义preprocessors,否则使用默认的preprocessors。 //默认preprocessors有 //['extend', 'statics', 'inheritableStatics', 'mixins', 'config'] var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(), //获取已经注册的preprocessors registeredPreprocessors = Class.getPreprocessors(), index = 0, preprocessors = [], preprocessor, preprocessors, staticPropertyName, process, i, j, ln; //继承Base类的静态方法,在ExtJS4.0中,通过Ext.define创建的类都是Base的直接 //或间接子类,后面在分析extend前置处理器时会详细说明,此处只把Base中的static //属性掺进newClass中。 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) { staticPropertyName = baseStaticProperties[i]; newClass[staticPropertyName] = Base[staticPropertyName]; } delete classData.preprocessors; //获取总是要注册的preprocessor(如extend)及创建的类注册的preprocessor for (j = 0, ln = preprocessorStack.length; j < ln; j++) { preprocessor = preprocessorStack[j]; if (typeof preprocessor === 'string') { preprocessor = registeredPreprocessors[preprocessor]; if (!preprocessor.always) { //创建类指定了要注册的preprocessor if (classData.hasOwnProperty(preprocessor.name)) { preprocessors.push(preprocessor.fn); } } else { //总是要注册的preprocessor preprocessors.push(preprocessor.fn); } } else { preprocessors.push(preprocessor); } } //类创建后的回调函数 classData.onClassCreated = onClassCreated; //preprocessor对类作增强后,类创建前的回调函数, //即将data中指定的配置信息掺进newClass的prototype中 classData.onBeforeClassCreated = function(cls, data) { onClassCreated = data.onClassCreated; delete data.onBeforeClassCreated; delete data.onClassCreated; //将data中指定的配置信息掺进newClass的prototype中 cls.implement(data); //调用类创建后的回调函数,主要是通过postpreprocessor对 newClass增强 if (onClassCreated) { onClassCreated.call(cls, cls); } }; //preprocessor process = function(cls, data) { preprocessor = preprocessors[index++]; //所有preprocessor调用结束后调用onBeforeClassCreated if (!preprocessor) { data.onBeforeClassCreated.apply(this, arguments); return; } //调用preprocessor直到所有preprocessor处理结束 if (preprocessor.call(this, cls, data, process) !== false) { process.apply(this, arguments); } }; process.call(Class, newClass, classData); return newClass; };
ExtJS4.0会首先加载Base,Class,ClassManager,Loader及其所依赖的类及其它一些工具类或方法,也就是ext-debug.js或ext.js中的代码,其中注册前置处理器的代码是在Class中定义的,代码如下:
registerPreprocessor: function(name, fn, always) { this.preprocessors[name] = { name: name,//前置处理器的名字 always: always || false,//是否总是要注册 fn: fn //当前前置处理器的回调函数 }; return this; }
在Class中注册的preprocessor有['extend', 'statics', 'inheritableStatics', 'mixins', 'config'],同时默认的前置处理器也是这几个。在Class中也定义了一个叫做setDefaultPreprocessorPosition的方法,其作用是将preprocessor插入到默认preprocessors的指定位置,而这个要插入的preprocessor必须是已经注册的(即通过registerPreprocessor方法注册到Class中的preprocessors),其定义如下:
setDefaultPreprocessorPosition: function(name, offset, relativeName) { var defaultPreprocessors = this.defaultPreprocessors, index; if (typeof offset === 'string') { if (offset === 'first') { defaultPreprocessors.unshift(name); return this; } else if (offset === 'last') { defaultPreprocessors.push(name); return this; } offset = (offset === 'after') ? 1 : -1; } index = Ext.Array.indexOf(defaultPreprocessors, relativeName); if (index !== -1) { Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name); } return this; }
其中offset取值可以是‘first’,‘last’(表示插入到队头还是队尾),‘before’,‘after’(相对relativeName插入在其前或其后)。
在ClassManager中给要创建的类注册了className前置处理器,主要作用是从data中获取$className给要创建的类添加$className属性,方便以后的调试及获取类的信息:
Class.registerPreprocessor('className', function(cls, data) { if (data.$className) { cls.$className = data.$className; //<debug> cls.displayName = cls.$className; //</debug> } }, true);
并通过
Class.setDefaultPreprocessorPosition('className', 'first');将其变为第一个preprocessor。
在Loader中给要创建的类注册了loader前置处理器,其主要作用是确保data中的extend,requires及mixins配置所指定的类已经加载,如果这些类还没加载则通过异步或同步的方式加载这些类,loader前置处理器会根据dependencyProperties(其值为['extend', 'mixins', 'requires'])中指定的preprocessor的顺序将data中对应的preprocessor要求使用的类名转换成实际的类,如会将
{ extend: 'Ext.MyClass', requires: ['Ext.some.OtherClass'], mixins: { observable: 'Ext.util.Observable'; } }转换成
{ extend: Ext.MyClass, requires: [Ext.some.OtherClass], mixins: { observable: Ext.util.Observable; } }loader前置处理器的代码如下:
Class.registerPreprocessor('loader', function(cls, data, continueFn) { var me = this, dependencies = [], className = Manager.getName(cls), i, j, ln, subLn, value, propertyName, propertyValue; /* Basically loop through the dependencyProperties, look for string class names and push them into a stack, regardless of whether the property's value is a string, array or object. For example: { extend: 'Ext.MyClass', requires: ['Ext.some.OtherClass'], mixins: { observable: 'Ext.util.Observable'; } } which will later be transformed into: { extend: Ext.MyClass, requires: [Ext.some.OtherClass], mixins: { observable: Ext.util.Observable; } } */ for (i = 0, ln = dependencyProperties.length; i < ln; i++) { propertyName = dependencyProperties[i]; if (data.hasOwnProperty(propertyName)) { propertyValue = data[propertyName]; if (typeof propertyValue === 'string') { dependencies.push(propertyValue); } else if (propertyValue instanceof Array) { for (j = 0, subLn = propertyValue.length; j < subLn; j++) { value = propertyValue[j]; if (typeof value === 'string') { dependencies.push(value); } } } else { for (j in propertyValue) { if (propertyValue.hasOwnProperty(j)) { value = propertyValue[j]; if (typeof value === 'string') { dependencies.push(value); } } } } } } if (dependencies.length === 0) { // Loader.historyPush(className); return; } //<debug error> var deadlockPath = [], requiresMap = Loader.requiresMap, detectDeadlock; /* Automatically detect deadlocks before-hand, will throw an error with detailed path for ease of debugging. Examples of deadlock cases: - A extends B, then B extends A - A requires B, B requires C, then C requires A The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks no matter how deep the path is. */ if (className) { requiresMap[className] = dependencies; detectDeadlock = function(cls) { deadlockPath.push(cls); if (requiresMap[cls]) { if (Ext.Array.contains(requiresMap[cls], className)) { Ext.Error.raise({ sourceClass: "Ext.Loader", msg: "Deadlock detected while loading dependencies! '" + className + "' and '" + deadlockPath[1] + "' " + "mutually require each other. Path: " + deadlockPath.join(' -> ') + " -> " + deadlockPath[0] }); } for (i = 0, ln = requiresMap[cls].length; i < ln; i++) { detectDeadlock(requiresMap[cls][i]); } } }; detectDeadlock(className); } //</debug> Loader.require(dependencies, function() { for (i = 0, ln = dependencyProperties.length; i < ln; i++) { propertyName = dependencyProperties[i]; if (data.hasOwnProperty(propertyName)) { propertyValue = data[propertyName]; if (typeof propertyValue === 'string') { data[propertyName] = Manager.get(propertyValue); } else if (propertyValue instanceof Array) { for (j = 0, subLn = propertyValue.length; j < subLn; j++) { value = propertyValue[j]; if (typeof value === 'string') { data[propertyName][j] = Manager.get(value); } } } else { for (var k in propertyValue) { if (propertyValue.hasOwnProperty(k)) { value = propertyValue[k]; if (typeof value === 'string') { data[propertyName][k] = Manager.get(value); } } } } } } continueFn.call(me, cls, data); }); return false; }, true);
经过上述一番注册,此时的前置处理器及顺序为['className','loader','extend','statics','inheritableStatics','mixins','config'],
然后就会按照顺序调用这些preprocessor。className与loader前置处理器及其作用在前面已经分析过,经loader处理后,data中extend,requires及mixins指定的类被加载且被转换成了实际的类,之后会调用extend前置处理器,代码如下:
Class.registerPreprocessor('extend', function(cls, data) { var extend = data.extend, //Base类是所有通过Ext.define创建的类的直接或间接父类 base = Ext.Base, basePrototype = base.prototype, prototype = function() {}, parent, i, k, ln, staticName, parentStatics, parentPrototype, clsPrototype; if (extend && extend !== Object) { parent = extend; } //如果没有在data中指定要继承的类,则默认继承Base类 else { parent = base; } parentPrototype = parent.prototype; prototype.prototype = parentPrototype; clsPrototype = cls.prototype = new prototype(); if (!('$class' in parent)) { for (i in basePrototype) { if (!parentPrototype[i]) { parentPrototype[i] = basePrototype[i]; } } } clsPrototype.self = cls; cls.superclass = clsPrototype.superclass = parentPrototype; //删除data中的extend属性 delete data.extend; // 继承父类中可被继承的属性或方法 parentStatics = parentPrototype.$inheritableStatics; if (parentStatics) { for (k = 0, ln = parentStatics.length; k < ln; k++) { staticName = parentStatics[k]; if (!cls.hasOwnProperty(staticName)) { cls[staticName] = parent[staticName]; } } } // 继承父类中的config信息 if (parentPrototype.config) { clsPrototype.config = Ext.Object.merge({}, parentPrototype.config); } else { clsPrototype.config = {}; } //父类被继承后的回调函数 if (clsPrototype.$onExtended) { clsPrototype.$onExtended.call(cls, cls, data); } //为当前类注册被继承后的回调函数 if (data.onClassExtended) { clsPrototype.$onExtended = data.onClassExtended; delete data.onClassExtended; } }, true);
其实裁剪一下,此继承的思想大致如下:
function extend(SubClass,SupClass) { function F(){} F.prototype = SupClass.prototype; SubClass.prototype = new F(); SubClass.superclass = SubClass.prototype.superclass = SupClass.prototype; return SubClass; }
statics前置处理器为类增加static属性或方法,代码如下:
Class.registerPreprocessor('statics', function(cls, data) { var statics = data.statics, name; for (name in statics) { if (statics.hasOwnProperty(name)) { cls[name] = statics[name]; } } delete data.statics; });
实现比较简单,直接将data中的statics中指定的属性或方法掺进newClass中即可。
inheritableStatics前置处理器设置可被继承的static属性,代码如下:
Class.registerPreprocessor('inheritableStatics', function(cls, data) { var statics = data.inheritableStatics, inheritableStatics, prototype = cls.prototype, name; inheritableStatics = prototype.$inheritableStatics; if (!inheritableStatics) { inheritableStatics = prototype.$inheritableStatics = []; } for (name in statics) { if (statics.hasOwnProperty(name)) { cls[name] = statics[name]; inheritableStatics.push(name); } } delete data.inheritableStatics; });
mixins前置处理器将指定类的prototype中的属性掺进当前类的prototype中,代码如下:
Class.registerPreprocessor('mixins', function(cls, data) { cls.mixin(data.mixins); delete data.mixins; });
Base类中定义了mixin静态方法,此静态方法被新创建的newClass继承,所以可以直接在newClass上调用mixin将data.mixins中的类掺进来,其代码如下:
mixin: flexSetter(function(name, cls) { var mixin = cls.prototype, my = this.prototype, i, fn; for (i in mixin) { if (mixin.hasOwnProperty(i)) { if (my[i] === undefined) { if (typeof mixin[i] === 'function') { fn = mixin[i]; if (fn.$owner === undefined) { this.ownMethod(i, fn); } else { my[i] = fn; } } else { my[i] = mixin[i]; } } else if (i === 'config' && my.config && mixin.config) { Ext.Object.merge(my.config, mixin.config); } } } if (my.mixins === undefined) { my.mixins = {}; } my.mixins[name] = mixin; })
其中flexSetter是Ext.Function.flexSetter中的一个工具函数,在整个ExtJS4.0框架中用到的地方很多,它接收一个需要两个参数的函数作参数,作用是返回一个函数,该函数接收两个参数,内部通过对返回函数参数调用flexSetter的形参函数将返回函数中的参数信息添加到调用对象中,说起来很拗口,代码如下:
flexSetter: function(fn) { return function(a, b) { var k, i; if (a === null) { return this; } if (typeof a !== 'string') { for (k in a) { if (a.hasOwnProperty(k)) { fn.call(this, k, a[k]); } } if (Ext.enumerables) { for (i = Ext.enumerables.length; i--;) { k = Ext.enumerables[i]; if (a.hasOwnProperty(k)) { fn.call(this, k, a[k]); } } } } else { fn.call(this, a, b); } return this; }; }
最后的一个前置处理器是config,将data中的config对象中的信息添加newClass的prototype中,此前置处理器会自动为config中的每一个没有配置setter,getter,apply方法的属性添加setter,getter与apply方法,并将这些方法临时存放到data中,其中apply方法在对象调用setter方法时会被调用,代码如下:
Class.registerPreprocessor('config', function(cls, data) { var prototype = cls.prototype; Ext.Object.each(data.config, function(name) { var cName = name.charAt(0).toUpperCase() + name.substr(1), pName = name, apply = 'apply' + cName, setter = 'set' + cName, getter = 'get' + cName; //添加setter方法 if (!(apply in prototype) && !data.hasOwnProperty(apply)) { data[apply] = function(val) { return val; }; } if (!(setter in prototype) && !data.hasOwnProperty(setter)) { data[setter] = function(val) { //调用相应的apply方法 var ret = this[apply].call(this, val, this[pName]); if (ret !== undefined) { this[pName] = ret; } return this; }; } //添加getter方法 if (!(getter in prototype) && !data.hasOwnProperty(getter)) { data[getter] = function() { return this[pName]; }; } }); //将data中的config合并到prototype的config中,并删除之 Ext.Object.merge(prototype.config, data.config); delete data.config; });
所有的上述前置处理器被调用后,就会调用刚才注册的onBeforeClassCreated方法,代码如下:
classData.onBeforeClassCreated = function(cls, data) { onClassCreated = data.onClassCreated; delete data.onBeforeClassCreated; delete data.onClassCreated; cls.implement(data); if (onClassCreated) { onClassCreated.call(cls, cls); } };
其中cls是被创建的newClass,其implement静态方法也是从Base继承而来,其作用是将参数对象(data)中的属性添加到调用对象(newClass)的prototype中,刚才在config前置处理器中生成并存放在data中的setter,getter及apply方法也会在此被添加到newClass的prototype中,代码如下:
implement: function(members) { var prototype = this.prototype, name, i, member, previous; //<debug> var className = Ext.getClassName(this); //</debug> for (name in members) { if (members.hasOwnProperty(name)) { member = members[name]; if (typeof member === 'function') { member.$owner = this; member.$name = name; //<debug> if (className) { member.displayName = className + '#' + name; } //</debug> } prototype[name] = member; } } if (Ext.enumerables) { var enumerables = Ext.enumerables; for (i = enumerables.length; i--;) { name = enumerables[i]; if (members.hasOwnProperty(name)) { member = members[name]; member.$owner = this; member.$name = name; prototype[name] = member; } } } }
至此整个newClass的创建已经完成,后面就是调用onClassCreated回调函数通过后置处理器对已经创建的类进行处理以方便对类的管理。
后置处理器的注册是在ClassManager中定义的,代码如下:
registerPostprocessor: function(name, fn, always) { this.postprocessors[name] = { name: name, always: always || false, fn: fn }; return this; }
同时在ClassManager也定义了setDefaultPostprocessorPosition,其作用与在Class中定义的setDefaultPreprocessorPosition的作用相同,为了将指定后置处理器放到指定的位置。在ClassManger中注册的后置处理器及其顺序为:['alias', 'singleton', 'alternateClassName'],并将它们设置为默认的后置处理器,在Loader中通过setDefaultPostprocessorPosition将在其中注册的uses后置处理器放到先前注册的默认后置处理器的后面,这些后置处理器都不是必须的。其中alias给新创建类设置别名,别名的前缀若为'widget.',则为创建的类(newClass)增加xtype静态属性,这样以后创建类的实例时就可以直接使用xtype来代替实际的类名了;singleton使创建的类成为单例类,经过此后置处理器的处理后,会new一个该类的实例(即new newClass())并注册到ClassManager中,以后每次创建一个该类的实例时就会直接返回这个已经被注册到ClassManager中的实例(单例);alternateClassName可以为类设置可替代的类名,这个后置处理器主要是为了兼容4.0之前的版本库以前而定义的,由于ExtJS4.0对命名空间及类名作了较大调整,所以可以通过设置alternateClassName的方式让以前版本的类名指向的是4.0版本中新类名所指向的实际类;而uses后置处理器列出了要和Ext.define的类一同加载的类,这些类在被实例化前可能并未加载。
alias后置处理器的定义如下:
Manager.registerPostprocessor('alias', function(name, cls, data) { var aliases = data.alias, widgetPrefix = 'widget.', i, ln, alias; if (!(aliases instanceof Array)) { aliases = [aliases]; } for (i = 0, ln = aliases.length; i < ln; i++) { alias = aliases[i]; //<debug error> if (typeof alias !== 'string') { Ext.Error.raise({ sourceClass: "Ext", sourceMethod: "define", msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string" }); } //</debug> this.setAlias(cls, alias); } // This is ugly, will change to make use of parseNamespace for alias later on for (i = 0, ln = aliases.length; i < ln; i++) { alias = aliases[i]; if (alias.substring(0, widgetPrefix.length) === widgetPrefix) { // Only the first alias with 'widget.' prefix will be used for xtype cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length); break; } } })
singleton后置处理器的定义如下:
Manager.registerPostprocessor('singleton', function(name, cls, data, fn) { fn.call(this, name, new cls(), data); return false; });
此处甚为巧妙,直接new一个cls的实例,之后后面的后置处理器(fn是调用后置处理器的函数,此处是递归调用)都会在这个对象上进一步处理,最后将这个实例注册到ClassManager中,以后要创建这个类的实例时直接返回的是这个实例,也就达到了单例的效果。
alternateClassName后置处理器的定义如下:
Manager.registerPostprocessor('alternateClassName', function(name, cls, data) { var alternates = data.alternateClassName, i, ln, alternate; if (!(alternates instanceof Array)) { alternates = [alternates]; } for (i = 0, ln = alternates.length; i < ln; i++) { alternate = alternates[i]; //<debug error> if (typeof alternate !== 'string') { Ext.Error.raise({ sourceClass: "Ext", sourceMethod: "define", msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string" }); } //</debug> this.set(alternate, cls); } });
uses后置处理器的定义如下:
Manager.registerPostprocessor('uses', function(name, cls, data) { var uses = Ext.Array.from(data.uses), items = [], i, ln, item; for (i = 0, ln = uses.length; i < ln; i++) { item = uses[i]; if (typeof item === 'string') { items.push(item); } } Loader.addOptionalRequires(items); });
所有上述的前置与后置处理器被调用结束后,就会将新创建的类注册到ClassManager中,代码如下:
set: function(name, value) { var targetName = this.getName(value); this.classes[name] = this.setNamespace(name, value); if (targetName && targetName !== name) { this.maps.alternateToName[name] = targetName; } return this; }
至此,整个类的创建便完成了。
1 楼
xyster8828
2011-10-14
百度框架哥.......