现在的位置: 首页 > 综合 > 正文

9、ExtJs——Ext基础架构–事件机制

2018年02月05日 ⁄ 综合 ⁄ 共 9754字 ⁄ 字号 评论关闭

对于事件

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

抱歉!评论已关闭.