Ext版本:4.2.1
从原生的浏览器事件处理开始
HTML元素如下:
<input id ='test' type='button' value='click me'>
事件处理如下:
/** 浏览器原生的事件机制 */ var e = document.getElementById("test"); // DOM 0 事件覆盖 e.onclick = function(event) { console.log("dom 0, event 1, event type: " + event.type); }; e.onclick = function(event) { console.log("dom 0, event 2, event type: " + event.type); }; // DOM 2 支持多处理事件 e.addEventListener("click",function(event){ console.log("dom 2, event 1, event type: " + event.type); },false); e.addEventListener("click",function(event){ console.log("dom 2, event 2, event type: " + event.type); },false);
2. 采用Ext对DOM和浏览器封装的处理方式
/** Ext封装了DOM和Event事件 */ var test = Ext.get("test"); /** * 注意这里调用的是Ext.EventManager的on()方法 * 而不是后面例子中Observer中的on()方法 * 但是二者类似 * * Ext.dom.Element中定义了on()方法,会调用Ext.EventManager的on() */ test.on("click", function () { console.log("ext handler1"); }); test.on("click", function () { console.log("ext handler2"); });
每一个Element继承自Ext.dom.Element,也就有了on()方法,会调用EventManager的on()方法,进行事件处理。
3. Ext对用户自定义事件的处理方法
3.1 类级别的事件监听和处理
在类中,定义监听事件,在每个对象中一旦触发此事件,则执行处理方法,类代码如下:
Ext.define('Application.SportsMan',{ config:{ userName: '' }, extend: "Ext.util.Observable", // 关键是这里 constructor: function(c) { var me = this; /** 添加事件名称或者定义事件名称 * 是不是多余?addListener已经有了事件名称了,但是的确可以省略 * * 不多余,起到统一声明的作用 */ me.addEvents('sing'); me.addEvents('run'); /** * 注意必须在addListener之前调用callParent * 因为在Observable的构造方法constructor中要进行一些初始化操作 */ me.callParent(arguments); /** 1 对类添加监听事件 */ this.addListener('sing',function(milk){ console.log(me.getUserName() + ' is singing..'); }); /** 定义多个处理事件 */ this.addListener('sing',function(milk){ console.log('note: ' + me.getUserName() + ' is singing again ..'); }); this.addListener('run',function(milk){ console.log(me.getUserName() + ' is running..'); }); } });
测试代码如下:
/** 类的加载方式1 var lx = Ext.create('Application.SportsMan',{}); lx.setUserName('liu xiang'); lx.fireEvent('run'); var ym = Ext.create('Application.SportsMan',{}); ym.setUserName('yao ming'); ym.fireEvent('sing'); */ /** 类的加载方式2 */ Ext.require('Application.SportsMan',function(SportsMan){ var lx = new SportsMan(); lx.setUserName('liu xiang'); lx.fireEvent('run'); var ym = new SportsMan(); ym.setUserName('yao ming'); ym.fireEvent('sing'); });
执行结果如下图:
3.2 对象级别的事件监听处理
在每个对象实例化之后,分别指定监听事件。和类的处理方式差别在于,同一个类的不同对象对同一事件可以有不同的处理方式。
类代码如下:
Ext.define('Application.Car',{ config:{ carName: '' }, extend: "Ext.util.Observable", constructor: function(c) { var me = this; /** * 同样这里的addEvents()可以省略 * 好处是:统一的对外声明监听方法的名称 */ me.addEvents('sing'); me.addEvents('run'); me.callParent(arguments); } });
测试代码如下:
Ext.require('Application.Car',function(Car){ var bc = new Car(); /** 2 给对象绑定事件监听 */ bc.on('run',function(){ // on 是addListener的简写 /** * 这里的getCarName()在方法setCarName() 调用之前 * 也没有问题,因为是回调函数 */ console.log(bc.getCarName() + ' is running..'); }); bc.on('run',function(){ console.log('note: ' + bc.getCarName() + ' run far away..'); }); bc.setCarName('ben chi'); bc.fireEvent('run'); var bmw = new Car(); bmw.addListener('sing',function(){ console.log(bmw.getCarName() + ' is singing..'); }); bmw.setCarName('bao ma'); bmw.fireEvent('sing'); /** 3 在对象的配置项中,定义监听 */ var pt = new Car({ carName: 'Phaeton', listeners:{ run: function(){ console.log(this.getCarName() + ' is running..'); } } }); pt.fireEvent('run'); });
给对象定义事件监听,可以在实例化之后调用on()方法,也可以直接定义在配置项listeners中。
小结:类或者对象的事件监听处理,比较简单。
继承Observable,这样就继承了addEvent()等方法,这里的Observable就是一个bus,是类级别的bus
调用addEvent('abc')相当于是向bus中添加了一个空对象'abc'
调用addListener('abc',function)相当于是在bus中找到对应的对象'abc',给他添加一个回调方法,如果没有则addEvent('abc')
调用fireEvent('abc')会在bus中找到对象'abc',然后挨个执行回调函数
所以,这里采用的是观察者(Observer)模式,所有继承自Observable的类都是Subject,在它上面注册(addListener)的每一个事件Event,都是观察者Observer!其实这里是Observable和Event的关系。
4. MVC结构中,Controller对Component、Store等的监听
这里涉及一个很重要的类 Ext.app.EventDomain。他把监听的范围从类级别扩大到类型级别,并且这个类型是可以自定义的。查找的时候,会在这个类型下面去查找。
定义了全局的bus ={},存放每个监听事件,结构类似于{run: [{Car: [Student,Teacher]},{Animal: [Student,Teacher]}]}
当Student类中对Student类的Car方法设置(listen方法)了监听事件的时候,会在bus 中添加一条记录
monitor方法,改变某个类的prototype的fireEventArgs方法。也就是重写了这个类从Observable中继承过来的fireEventArgs
当Car.fireEvent('run')的时候,会调用上面的fireEventArgs,会调用dispatch()方法
dispatch()方法,会根据方法名字'run'在bus中找到事件对象,然后根据target,也就是Car查找对应的Controllers,也就是[Student,Teacher],然后执行每个Controller中的处理方法
举例如下:
被监听类:
Ext.define('Application.FooC', { extend: 'Ext.app.Controller', id: 'FooC', init: function(){ this.fireEvent('barOpenWindow', 'Foo'); } });
监听类1:
Ext.define('Application.BarC', { extend: 'Ext.app.Controller', id: 'BarC', init: function() { this.listen({ controller: { // 详见Ext.app.domain.Controller '#FooC': { barOpenWindow: 'openWindow' } } }); }, openWindow: function(who) { console.log(who + ' is coming..'); } });
监听类2:
Ext.define('Application.BzzC', { extend: 'Ext.app.Controller', id: 'BzzC', init: function() { this.listen({ controller: { // 注释1 这就是分类 '#FooC': { barOpenWindow: 'openWindow' } } }); }, openWindow: function(who) { console.log(who + ' is coming..'); } });
类之间的结构关系:
所有的Controller都继承自Ext.app.Controller,而它use了Ext.app.domain.Controller
Ext.app.domain.Controller是单例的
Ext.app.domain.Controller继承自Ext.app.EventDomain,并且定义type:'controller',这个就是注释1的关键字
Ext.app.domain.Controller还定义了idProperty: 'id',在match匹配的时候,会按照id匹配,所以Controller是按照id查找的
Ext.app.domain.Controller在构造的时候,会调用me.monitor(Ext.app.Controller);也就是Ext.app.EventDomain的monitor()方法
所以,这种模式是中介者(Mediator)模式,我们的Controller都是colleague,而domain.Controller就是Mediator!
monitor代码如下:
monitor: function(observable) { var domain = this, prototype = observable.isInstance ? observable : observable.prototype, fireEventArgs = prototype.fireEventArgs; domain.monitoredClasses.push(observable); prototype.fireEventArgs = function(ev, args) { // 这里的原型就是observable var ret = fireEventArgs.apply(this, arguments); if (ret !== false) { ret = domain.dispatch(this, ev, args); // 当fireEvent的时候,执行 } return ret; }; }
dispath代码如下:
dispatch: function(target, ev, args) { // 注意:target 是被监听类 var me = this, bus = me.bus, // 见下面1图 selectors = bus[ev], selector, controllers, id, events, event, i, ln; if (!selectors) { return true; } // Loop over all the selectors that are bound to this event for (selector in selectors) { // Check if the target matches the selector if (selectors.hasOwnProperty(selector) && me.match(target, selector)) { // Loop over all the controllers that are bound to this selector controllers = selectors[selector]; for (id in controllers) { if (controllers.hasOwnProperty(id)) { // Loop over all the events that are bound to this selector events = controllers[id]; for (i = 0, ln = events.length; i < ln; i++) { event = events[i]; // Fire the event! if (event.fire.apply(event, args) === false) { return false; } } } } } } return true; }