对于事件
- 追朔到原始的事件模型,也就是0级DOM事件模型。非常通俗的说,就是给HTML元素里加一个事件,这种方法有一个硬伤,就是不能同时对元素注册多个处理函数。
- W3C的专家定义2级DOM事件模型也就产生了,也就是所谓的标准事件模型。API如下定义:
- element.addEventListener(type,listener,useCapture);
- element.removeEventListener(type,listener,useCapture);
- 这又牵扯到了事件流,也就是事件的传播。W3C的专家把事件分成了3个阶段:(1)捕获;(2)目标;(3)冒泡;
- 事件对象(event)这个对象是W3C的标准。但是IE不支持,只能用window.event去获取,这里又引出了一个最头疼的问题,就是浏览器差异。
//1、在早些时候,是不存在事件这个概念的,开发者用javascript的2个函数去模拟事件的机制(window.setTimeOut/window.setInterval) //2、由于很多原因,比如效率太低,人们开始发明了最原始的0级DOM事件模型,为元素添加一个事件,在事件上绑定一个处理函数 //注意:这种模型有一个致命的硬伤:就是不能为元素的事件添加多个处理函数 /** * 0级DOM事件模型 window.onload = function(){ var inp = document.createElement('input'); inp.id = 'inp1'; inp.value = '点击'; inp.type = 'button'; inp.onclick = function(){ alert('执行了'); }; inp.onclick = function(){ //覆盖上一个 alert(this == window); //这个为false alert(this == inp); //这个为true alert('我也执行了'); }; document.body.appendChild(inp); }; */ /** * 如果在html或jsp页面上,如下 * <script type="javascript/text"> * function test1(){ * alert(this == window) //这个为true * alert('1111'); * } * function test2(){ * alert('22222'); * } * </script> * <body> * <input type="button" value="我也点击" onclick="test1();test2();" * </body> * 这两个函数都执行,这种形式的作用域是window */ // //function test1(){alert(this == window);}; //function test2(){alert(222);}; //3、在0级DOM事件模型之后W3C成立,觉得0级事件模型不好,推出了2级DOM事件模型(标准DOM事件模型) // element.addEventListener(type,listener,useCapture); // element.removeEventListener(type,listener,useCapture); //window.onload = function(){ /** var inp = document.createElement('input'); inp.id = 'inp1'; inp.value = '点击'; inp.type = 'button'; //type :事件类型 listener:这个事件的绑定函数 useCapture(boolean):(事件传播:true=捕获/false=冒泡) inp.addEventListener('click',test1,false); inp.removeEventListener('click',test1,false); inp.addEventListener('click',test2,false); document.body.appendChild(inp); */ //IE浏览器(6\7\8版本):使用attachEvent();detachEvent();9、10已经支持W3C标准了 /** var inp = document.createElement('input'); inp.id = 'inp1'; inp.value = '点击'; inp.type = 'button'; inp.attachEvent('onclick',test1); inp.detachEvent('onclick',test1); inp.attachEvent('onclick',test2); document.body.appendChild(inp); */ //}; //对于事件的传播机制:W3C:1、捕获 2、目标()命中 3、冒泡 //W3C提供了一个关键字event 事件对象 /ie678:window.event window.onload = function(){ var inp = document.createElement('input'); inp.id = 'inp1'; inp.value = '点击'; inp.type = 'button'; inp.addEventListener('click',function(event){ alert('input执行了'); event.stopPropagation(); //阻止冒泡的发生 },false); var div = document.createElement('div'); div.addEventListener('click',function(){alert('div执行了')},false); document.body.addEventListener('click',function(){alert('body执行了')},false); div.appendChild(inp); document.body.appendChild(div); };
对于事件流,也就是事件的传播,W3C的专家把事件分成了3个阶段:(1)捕获;(2)目标;(3)冒泡;
高级事件
- 基本事件是什么?就类似于click、keypress、focus、mouseover等这些事件都是浏览器定义好的内置事件,我们直接使用即可。对于高级事件,无非就是自己去设计一个事件,比如实际项目中,通常都伴随业务逻辑,可能是增删改查等,这些事件都是非原生事件,也就是浏览器无法自行判断触发的,但是我们确实有需求去实现它们。
- 对于如何实现自定义事件,还需要了解标准事件的使用原理,然后做一个简单地分析,考虑3点:
- 如何注册事件?
- 如何触发事件?
- 如何删除事件?
- 其实浏览器的事件内部使用的是javascript经典的观察者模式去实现的,那么我们也可以模拟一个观察者模式,为自己设计事件!
模拟实现:使用观察者模式模拟事件
设计分析:
- 首先需要一个事件的定义者,类似浏览器一样能自动分辨出所触发的任意内置事件,我们叫他Observable,它是我们要定义的类;然后应该有一个触发事件的对象(就类似浏览器里的元素),也就是事件源,这个类可以是Observable的子类。
- 第二,Observable这个对象的实例可能会有多个可以触发的事件,我们随意定义2个自己的事件,开始‘start’、停止‘stop’,这2个自定义的事件名称也就是我们要进行的事件。这2个事件名称必须要属于Observable这个类
- 第三,因为仅仅定义名称是不行的,需要为自己定义的事件名称绑定相关的函数,当然函数可以是多个(一个事件可以绑定多个函数)然后去执行它们。你需要有一个数据结构负责维护事件名称与所绑定函数直接的关系
- 第四,新增事件类型,也就是去添加你自己的事件名称
- 第五,添加监听函数,也就是做一个事件名称与函数的绑定方法
- 第六,相应的也应该有一个移除事件的方法
- 第七,触发事件,就是调用这个事件名称所对应的所有函数即可。
- 最后,给函数起一个别名,从而方便开发者使用。
//利用观察者模式去实现自定义的事件 //1、首先我们需要一个事件的定义者,类似浏览器一样能自动分辨 //1、由于浏览器自己能定义内置的事件(click/blur...) //我们也应该有一个类似于浏览器这样的类,这个类自己去内部定义一些事件(自定义事件) var Observable = function(){ //承装自己定义的事件类型的 this.events = ['start','stop']; //我们应该设计一种数据类型,这种数据类型就可以去维护自定义事件类型和相关绑定函数的关系,结构如下 //'start':[fn1,fn2...], //'stop':[] this.listeners = { }; }; //2、添加新的自定义事件类型 Observable.prototype.addEvents = function(eventname){ this.events.push(eventname); }; //3、为自己的事件类型绑定相应的函数(添加事件监听) Observable.prototype.addListener = function(eventname,fn){ //做一个容错处理 if(this.events.indexOf(eventname) == -1){ this.addEvents(eventname); } //到这里,事件类型肯定存在,那么 var arr = this.listeners[eventname]; //如果当前这个函数数组不存在,那么我们要为这个事件类型绑定新添加的函数 if(!arr){ arr = [fn]; } else { //如果存在,当前这个事件类型所对应的函数数组不为空,已绑定过函数 if(arr.indexOf(fn) == -1){ arr.push(fn); } } //重新维护一下事件类型和所绑定的函数数组的关联关系 this.listeners[eventname] = arr; }; //4、移除事件监听 Observable.prototype.removeListener = function(eventname,fn){ //如果要移除的事件类型,在对象里没有定义 if(this.events.indexOf(eventname) == -1){ return ; } //到这一步,就是你要移除的事件类型是我当前对象里存在的 var arr = this.listeners[eventname]; if(!arr){ return ; } //到这一步,证明arr里面是有绑定函数的 //判断 如果当前fn函数在我的函数数组中存在,就移除 if(arr.indexOf(fn) != -1){ arr.splice(arr.indexOf(fn),1); } }; //5、如何让事件触发:就是调用这个事件类型所对应的所有函数执行即可 Observable.prototype.fireEvent = function(eventname){ //如果当前传递的事件为空或者事件类型不存在,直接返回 if(!eventname || (this.events.indexOf(eventname) == -1)){ return ; } //到这一步,一定存在这个事件 var arr = this.listeners[eventname]; if(!arr){ return ; } for(var i = 0,len = arr.length;i<len;i++){ var fn = arr[i]; fn.call(fn,this); } }; //javascript的习惯,给原型对象的方法起一个别名,方便使用 Observable.prototype.on = Observable.prototype.addListener; Observable.prototype.un = Observable.prototype.removeListener; Observable.prototype.fr = Observable.prototype.fireEvent; //Observable相当于一个浏览器 var ob = new Observable(); // 被观察者 / 观察者 //子类继承Observable //观察者 var fn1 = function(){ alert('fn1.......'); }; ob.on('start',fn1); var fn2 = function(){ alert('fn2.......'); }; //ob.on('stop',fn2); ob.on('start',fn2); ob.un('start',fn1); ob.fr('start'); //ob.fr('stop'); ob.on('run',function(){ alert('run...'); }); ob.fr('run');
//Ext.util.Observable 类是为了为开发者提供一个自定义事件的接口
//观察者模式:(类比:报社、订阅者)被观察者、观察者
//Ext.util.Observable -->被观察者(具有触发)
//所有继承(混入)Ext.util.Observable类的对象(子类) --->观察者(具有订阅和退订)
事件工具类:
Ext.EventManager,对于事件,我们需要屏蔽浏览器的差异,需要一个事件管理器,用于屏蔽一切浏览器差异,这个类,就是为了屏蔽浏览器差异,暴漏统一的调用接口。是一个静态工具类
//Ext.EventManager:封装浏览器自带的事件,并且解决了浏览器的差异问题 var MyExt = {}; MyExt.EventManager = { //添加监听 addListener:function(el,ename,fn,useCapture){ if(el.addEventListener){ el.addEventListener(ename,fn,useCapture) } else if(el.attachEvent){ el.attachEvent('on' + ename,fn); } }, //移除监听 removeListener:function(el,ename,fn,useCapture){ if(el.removeEventListener){ el.removeEventListener(ename,fn,useCapture) } else if(el.detachEvent){ el.detachEvent('on' + ename,fn); } } //w3c ====event /ie window.event }; MyExt.EventManager.on = MyExt.EventManager.addListener; MyExt.EventManager.un = MyExt.EventManager.removeListener; window.onload = function(){ var btn = document.getElementById('btn'); MyExt.EventManager.on(btn,'click',function(){ alert('执行了'); },false); MyExt.EventManager.on(btn,'click',function(){ alert('又执行了'); },false); };
对于事件系统,设计了两套机制,一套机制做了对自定义事件的处理,另一套机制做了对原生浏览器事件的差异处理。如果现在有一个需求,就是点击按钮,然后触发一个自定义的事件,那该如何去做?
很简单,就是把这两套系统综合起来,无非就是点击按钮的时候里面调用一下自己所定义的事件“fire”方法即可。
对于Ext来说,它的事件机制的核心设计就是我们模拟的这两套机制,简单做一个说明:Ext的事件分为浏览器事件和自定义事件。通过Observable接口提供了一套完全自定义的事件机制,然后再通过EventManager事件工具类对原生事件的一次封装,屏蔽了浏览器之间的差异。最后保证Observable、EventManager它们两套机制暴漏出完全相同的接口,这两套机制相互配合,相互并行。可以随意的通过on、un方法绑定原生事件或自定义事件来完成事件的处理。从而实现了非常强大的功能。
熟练使用Ext事件:
- 为底层元素注册事件:on 、un方法使用:
- Ext.EventManager.on(el,eventName,fn,[scope,options...])
- 常用options:preventDefault、stopPropagation、delay、single
- Ext.EventManager.un(同上)
- 三种绑定事件的方式:
- Ext.EventManager.on(el,ename,fn);
- Ext.EventManager.on(el,{ename1:fn1,ename2:fn2});
- Ext.EventManager.on(el,{ename1:{fn:fun},ename2:{fn:fun}});
为Ext的UI组件绑定事件:两种方式
在listeners里注册事件(组件里的配置项),单独为组件批量注册事件
Ext.onReady(function(){ //为Ext的UI组件绑定事件 //1:直接在组件内部添加listeners配置项即可 /** var win = Ext.create('Ext.window.Window',{ title:'UI组件之事件实例1', width:400, height:300, renderTo:Ext.getBody(), tbar:[{ text:'dianjiwo', id:'aa' }], listeners:{ //在这个配置项对象中加入事件即可 show:function(){ alert('我展示出来了'); }, close:function(){ alert('关闭事件'); }, render:function(){ alert('组件渲染的时候执行事件'); }, click:{ element:'el', fn:function(){alert('点击组件内部的body');} } } }); win.show(); //即使没有这一句render也会执行 */ //2:使用组件的引用为组件绑定一些事件 var win = Ext.create('Ext.window.Window',{ title:'UI组件之事件实例1', width:400, height:300, renderTo:Ext.getBody() }); //Ext.Window混入了Ext.Observable类,可以使用其on方法 // win.on('show',function(){ // alert('我展示出来了'); // ); win.on({ 'show':function(){ alert('我展示出来了'); }, 'close':function(){ alert('关闭事件'); }, 'render':function(){ //这种方法绑定render事件不会执行,太迟了 alert('组件渲染的时候执行事件'); } }); win.show(); });
要注意render事件(渲染)的执行时机。组件创建完在绑定的方法,render事件不会执行
自定义事件的注册
Ext.onReady(function(){ //Ext.util.Observable 自定义事件类 //1:最简单的自定义事件 /** var win = Ext.create('Ext.window.Window',{ title:'简单的自定义事件', width:400, height:300, renderTo:Ext.getBody(), listeners:{ show:function(){ //1.3触发自定义事件的时机 win.fireEvent('myEvent'); } } }); //1.1添加事件类型 win.addEvents('myEvent'); //1.2添加事件的监听 win.on('myEvent',function(){ alert('my event...'); }); win.show(); */ //2、为自己定义的类去添加事件的支持 /** Ext.define('Employee',{ mixins:{ observable:'Ext.util.Observable' }, constructor:function(config){ this.mixins.observable.constructor.call(this,config); this.addEvents( 'fired', 'quit' ); } }); var newEmployee = new Employee({ listeners:{ quit:function(){ alert('has quit!'); } } }); newEmployee.fireEvent('quit'); }); */ //3、单次运行监听器的使用,single配置项在组件中的用途 /** var win = Ext.create('Ext.window.Window',{ title:'我是单次执行监听器的使用', width:400, height:300, renderTo:Ext.getBody(), listeners:{ render:function(){ alert('把组件渲染到body上,整个过程只执行一次'); }, single:true, //当前这个事件监听执行一次之后就自动销毁了 delay:3000 //延迟执行事件监听的执行 } }); win.show(); */ //4、对于事件的挂起和恢复示例 /** var btn1 = Ext.create('Ext.button.Button',{ text:'挂起', renderTo:Ext.getBody(), handler:function(){ btn3.suspendEvents(); } }); var btn2 = Ext.create('Ext.button.Button',{ text:'恢复', renderTo:Ext.getBody(), handler:function(){ btn3.resumeEvents(); } }); var btn3 = Ext.create('Ext.button.Button',{ text:'按钮', renderTo:Ext.getBody(), listeners:{ 'mouseover':function(){ alert('执行了。。。'); } } }); */ //5、事件的转发机制 var win = Ext.create('Ext.window.Window',{ title:'事件的转发', width:400, height:300, renderTo:Ext.getBody(), listeners:{ myEvent:function(){ alert('我是自定义事件,转发给btn'); } } }); var btn = Ext.create('Ext.Button',{ text:'按钮', renderTo:Ext.getBody(), handler:function(){ btn.fireEvent('myEvent'); } }); win.show(); //事件的转发机制:1:转发给的对象 2:转发的事件类型数组 win.relayEvents(btn,['myEvent']); });
最后的总结:
Ext的事件主要做了三个最重要的工作:
- (1)屏蔽了浏览器的差异
- (2)原生事件和自定义事件可以并行的自由使用
- (3)强大的扩展性
可以说我们这个Ext,是运行在Observable这个类之上的。Ext中大部分类都需要混入Ext.util.Observable,从而提供对于事件的支持