对于事件
- 追朔到原始的事件模型,也就是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="我也点击" οnclick="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,从而提供对于事件的支持