jQuery的事件模型
前几天自己着重读了jQuery1.11.1的源码,又结合了之前对DE事件模型的分析,最后也实现一个简陋的事件模型。
jQuery的事件系统离不开jQuery的缓存系统。
jQuery的第一代缓存是直接将数据存储在 缓存体 这个数据结构中,但是需要在元素上添加一个uuid来作为标示,
标记在缓存体中的位置。但是仔细想想,就会发现,如果对window或者document进行事件侦听,就会在这两个
对象上添加额外属性,会造成全局污染,不是很好。
所以jQuery第二代缓存系统应运而生,这次不对元素进行添加属性,而是判断元素的valueOf()方法的返回值,如果没有返回值是
对象,则说明缓存体中并没有该元素的缓存数据,进而使用ECMA5的Object.defineProperty来对valueOf方法进行重写,并返回
一个guid,作为访问缓存体的钥匙。
简单讲述了缓存系统,现在着重讲解下jQuery的事件系统:
主要使用了几个数据结构,即元素的缓存体,Event构造函数,和Handler构造函数。
当使用bind(el,type,fn)添加回调时,会根据Handler构造函数构造一个handler实例,在我的具体实现中,参数fn可能是一个函数,也可能
是一个对象,若是对象,则标记这个回调函数的功能--once函数或者throttle函数或delay函数。 其次就是对fn的封装,在库中,fn的包装函数
实现了新事件对象的创建,以及对新创建的事件对象的修补,并调整了在回调中this的指向。最后将该handlerObj存入该元素对应的缓存体中,
并用addEvent绑定事件。
使用unbind移除回调也比较简单,无非是移除缓存,移除回调。
trigger触发回调主要就是传入参数的处理,执行带有参数的回调。
现附上简单的实现:
// HandlerObject constructor
function Handler(config){
this.handler = config.handler;
this.special = config.special; //特殊的回调,ex. once函数,throggle函数等等,原回调放在此处,handler放包裹后的回调
this.type = config.type;
this.namespace = config.namespace;
this.data = config.data;
this.once = config.once;
this.delay = config.delay;
this.throttle = config.throttle;
this.stop = config.stop; // 取消默认和冒泡
this.stopBubble = config.stopBubble;
this.preventDefalut = config.preventDefalut;
}
//typeEvents=[handlerObj1,handlerObj2,...]
function execHandlers(el,event,args,context){ // 若args不为空,则为自定义事件出发,trigger
if(el.nodeValue == 3 || el.nodeValue == 8) return;
var elData,events,handlers,typeEvents,ret,
flag = true;
context = context || el;
//获取缓存对象
elData= S._data(el);
if(!elData || !elData['events'])return;
events = elData['events'];
handlers = elData['handlers'];
if(!events[event.type])return;
typeEvents = events[event.type]; // 如果其中一个回调执行出错,函数库也不会抛错
for(var i = 0,len=typeEvents.length;i<len;i++){ // 捕获错误,如果一个事件绑定多个回调,其中一个回调出错不会影响其他回调执行
try{
// 如果设置var isImmediatePropagationStopped,那么执行两件事:
// 1,停止执行该元素同事件的其他处理函数
// 2,停止冒泡
if(event.isImmediatePropagationStopped()) break;
ret = execHandler(el,event,typeEvents[i],args,context);
if(ret == false){
flag = false
}
}catch(e){
setTimeout(function(){
throw Error(e); // 异步抛出错误
},0); if(i < len && i+1 <len){
i++;
ret = execHandler(el,event,typeEvents[i],args,context);
if(ret == false){
flag = false
}
}
}
}
if(!flag){
event.preventDefault();
event.stopPropagation();
}
return;
}
function execHandler(el,event,handlerObj,args,context){
var handler = handlerObj.handler,
type = event.type,
special = handlerObj.special,
stop = handlerObj.stop,
preventDefault = handlerObj.preventDefalut,
stopBubble = handlerObj.stopBubble,
data = handlerObj.data,
once = handlerObj.once,
delay = handlerObj.delay, // 时延
throttle = handlerObj.throttle; //最小间隔时间
if(handlerObj.type && type !== handlerObj.type) return; if(!handler || !S.isFunction(handler))return; if(stop){
event.preventDefalut();
event.stopPropagation();
}
if(preventDefault){
event.preventDefalut();
}
if(stopBubble){
event.stopPropagation();
} if(once){
var onceHandler = function(event,args){
return S.once(handler,context,event,args);
};
return onceHandler.call(context,event,args);
}
if(delay && S.isNumber(delay)){
var delayHandler = function(event,args){
return S.delay(handler,context,delay,event,args);
}
return delayHandler.call(context,event,args);
}
if(throttle && S.isNumber(throttle)){
var throttleHandler = function(event,args){
return S.throttle(handler,context,throttle,event,args);
}
return throttleHandler.call(context,event,args);
} if(handler){
return handler.call(context,event,args);
}
return;
} function returnTrue(){
return true;
}
function returnFalse(){
return false;
}
//Event constructor
function Event(e){ //传入事件参数
this.originalEvent = e;
this.isPreventDefault = returnFalse;
this.isStopPropagation = returnFalse;
this.isImmediatePropagationStopped = returnFalse;
var type = e.type;
if(/^(\w+)\.(\w+)$/.test(type)){
this.type = RegExp.$1;
this.namespace = RegExp.$2
}else{
this.type = type;
this.namespace = '';
}
} Event.prototype = {
preventDefault: function(){
var e = this.originalEvent;
if(e.preventDefalut){
return e.preventDefault();
}
e.returnValue = false;
this.isPreventDefault = returnTrue;
return;
},
stopPropagation: function(){
var e = this.originalEvent;
if(e.stopPropagation){
return e.stopPropagation();
}
e.stopBubble = true;
this.isStopPropagation = returnTrue;
return;
},
stopImmediatePropagation: function(){
this.stopPropagation();
this.isImmediatePropagationStopped = returnTrue;
}
};
//事件修复
function fixEvent(event){
var i, prop, props = [], originalEvent = event.originalEvent; props = props.concat('altKey bubbles button cancelable charCode clientX clientY ctrlKey currentTarget'.split(' '));
props = props.concat('data detail eventPhase fromElement handler keyCode layerX layerY metaKey'.split(' '));
props = props.concat('newValue offsetX offsetY originalTarget pageX pageY prevValue relatedTarget'.split(' '));
props = props.concat('screenX screenY shiftKey target toElement view wheelDelta which'.split(' '));
for(i=props.length;--i;){
event[props[i]] = originalEvent[props[i]];
} if(!event.target){
event.target = event.srcElement;
} if(event.target.nodeType == 3){
event.target = event.target.parentNode;
} if(!event.relatedTarget){
event.relatedTarget = event.fromElement === event.target? event.toElement : event.fromElement;
} if(!event.which && (event.charCode || event.keyCode)){
event.which = event.charCode ? event.charCode : event.keyCode ? event.keyCode : null;
} if(!event.pageX || !event.pageY){
event.pageX = event.clientX + (doc.documentElement && doc.documentElement.scrollLeft || doc.body && doc.body.scrollLeft || 0)
- (doc.documentElement && doc.documentElement.clientLeft || doc.body && doc.body.clientLeft || 0);
event.pageY = event.clientY + (doc.documentElement && doc.documentElement.scrollTop || doc.body && doc.body.scrollTop || 0)
- (doc.documentElement && doc.documentElement.clientTop || doc.body && doc.body.clientTop || 0);
} if(!event.which && event.button != undefined){ //ie下 0 无动作, 1 左键 ,2 右键, 4 中间键
event.which = (event.button & 1) ? 1 : (event.button & 2) ? 3 : (event.button & 4) ? 2 : 0;
}
return event;
} function bind(el,type,fn){
if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return; var elData= S._data(el),events,handlers,typeEvents;
if(!elData) {
S._lockData(el); //开辟缓存
elData = S._data(el);
}
if(!elData['events']){
elData['events'] = {};
}
events = elData['events'];
handlers = elData['handlers']; // 目前先不对其赋值
if(!events[type]){
events[type] = [];
}
typeEvents = events[type]; var handlerObj;
if(S.isFunction(fn)){
handlerObj = new Handler({handler: fn});
}else if(S.isObject(fn)){
handlerObj = new Handler(fn);
}else{
return;
} handlerObj.handlerHook = function(event,args){ // 函数钩子,用于unbind删除回调函数
event = event || window.event;
var e = new Event(event);
e = fixEvent(e);
execHandlers(el,e,args,el);
}; if(!typeEvents || !typeEvents.length)
addEvent(el,type,handlerObj.handlerHook); typeEvents.push(handlerObj);
} function unbind(el,type,fn){
var newEvents = [];
if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return; var elData= S._data(el),events,handlers,typeEvents; if(!elData || !elData['events'])return; if(arguments.length == 1){ // 删除该元素所有缓存 事件
for(var i in elData['events']){
if(elData['events'].hasOwnProperty(i)){
for(var j=0,len=elData['events'][i].length;j<len;j++){
removeEvent(el,i,elData['events'][i][j].handlerHook);
}
}
}
S._unData(el);
} events = elData['events'][type];
newEvents = events.concat(); if(arguments.length == 2 && events){
try{
for(var i= 0,len=events.length;i<len;i++){
removeEvent(el,type,events[i].handlerHook);
}
}catch(e){
throw new TypeError('哎呀啊,解除回调出现意外')
} events = {};
delete elData[type];
} if(arguments.length == 3){
for(var i= 0,len=events.length;i<len;i++){
if(events[i].handler === fn){
try{
removeEvent(el,type,events[i].handlerHook);
}catch(e){
throw new TypeError('哎呀啊,解除回调出现意外')
}
newEvents.splice(i,1);
}
}
}
elData['events'][type] = events = newEvents;
} function trigger(el,type,args){
if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return; var elData= S._data(el),events,handlers,typeEvents; if(!elData || !elData['events'] || !elData['events'][type])return;
events = elData['events'][type]; var handlerObj,event;
event = {
target: el,
type: type,
data: args
};
for(var len=events.length;--len>=0;){
handlerObj = events[len];
handlerObj.handlerHook(event,args);
}
}
jQuery的事件模型的更多相关文章
- jQuery (01) 浏览器的事件模型
		
浏览器的事件模型 由网景公司引入的 DOM0 级事件模型 把事件处理程序绑定到 DOM 元素的属性上: ele.onclick(); ele.onDOMContentLoad(); ele.onloa ...
 - Javascript事件模型系列(三)jQuery中的事件监听方式及异同点
		
作为全球最知名的js框架之一,jQuery的火热程度堪称无与伦比,简单易学的API再加丰富的插件,几乎是每个前端程序员的必修课.从读<锋利的jQuery>开始,到现在使用jQuery有一年 ...
 - javascript中0级DOM和2级DOM事件模型浅析                                                    分类:            C1_HTML/JS/JQUERY             2014-08-06 15:22    253人阅读    评论(0)    收藏
		
Javascript程序使用的是事件驱动的设计模式,为一个元素添加事件监听函数,当这个元素的相应事件被触发那么其添加的事件监听函数就被调用: <input type="button&q ...
 - Javascript事件模型系列(一)事件及事件的三种模型
		
一.开篇 在学习javascript之初,就在网上看过不少介绍javascript事件的文章,毕竟是js基础中的基础,文章零零散散有不少,但遗憾的是没有看到比较全面的系列文章.犹记得去年这个时候,参加 ...
 - JS的事件模型
		
之前对事件模型还是比较清楚的,许多概念都清晰映射在脑海中.工作之后,一方面使用的局限性,二是习惯于用框架中的各种事件监听方式,简单即方便,久而久之,事件的一些概念开始淡出记忆中,就像我现在已经开始淡忘 ...
 - JavaScript DOM事件模型
		
早期由于浏览器厂商对于浏览器市场的争夺,各家浏览器厂商对同一功能的JavaScript的实现都不进相同,本节内容介绍JavaScript的DOM事件模型及事件处理程序的分类. 1.DOM事件模型.DO ...
 - js 事件模型
		
说到事件,就要追溯到网景与微软的“浏览器大战”了.当时,事件模型还没有标准,两家公司的实现就是事实标准.网景在Navigator中实现了“事件捕获”的事件系统,而微软则在IE中实现了一个基本上相反的事 ...
 - ExtJS框架基础:事件模型及其常用功能
		
前言 工作中用ExtJS有一段时间了,Ext丰富的UI组件大大的提高了开发B/S应用的效率.虽然近期工作中天天都用到ExtJS,但很少对ExtJS框架原理性的东西进行过深入学习,这两天花了些时间学习了 ...
 - 【JQuery】事件冒泡及使用jQuery阻止
		
(1)什么是事件起泡 首先你要明白一点,当一个事件发生的时候,该事件总是有一个事件源,即引发这个事件的对象,一个事件不能凭空产生,这就是事件的发生. 当事件发生后,这个事件就要开始传播.为什么要传播呢 ...
 
随机推荐
- webpack如何与gulpfile联合的使用
			
一.对webpack的一些理解 webpack支持CommonJS的书写形式. CommonJS指一个文件一个模块,但会一次性加载(即同步加载),但在浏览器端不适用这种方式,加载速率什么的,于是引入了 ...
 - myBatis中 collection 或 association 联合查询 中column 传入多个参数值
			
下面是一个树形结构表自连接 联合查询 Demo <resultMap id="BaseResultMap" type="com.maidan.daas.entit ...
 - 提取bmp图片的颜色信息,可直接framebuffer显示(c版本与python版本)
			
稍微了解了下linux的framebuffer,这是一种很简单的显示接口,直接写入像素信息即可 配置好的内核,会有/dev/fbn 的接口,于是想能否提前生成一个文件,比如logo.fb,里面仅包含像 ...
 - JVM 备注
			
一.堆内存分布: JAVA 分为堆内存和栈内存,GC主要针对堆内存 1)Young: 存放新生内存对象 1.1)Eden JVM 刚开始分配的对象 1.2)Survivor1(from) 1.3)Su ...
 - 人生苦短,我用python——当我在玩python的时候我玩些什么
			
文章背景 家里的第一台电脑是在2006年夏天买的,10年上大学之后基本上就没人用,过没两年就当二手卖给一个熟人. 弟弟小我10岁,今年刚上初一.他在我毕业前半年就整天用妈妈的手机发短信给我,问我什么时 ...
 - AutoMapper 最佳实践
			
AutoMapper 是一个基于命名约定的对象->对象映射工具. 只要2个对象的属性具有相同名字(或者符合它规定的命名约定),AutoMapper就可以替我们自动在2个对象间进行属性值的映射.如 ...
 - 招聘.NET开发人员(截止于2015-06-15)
			
文章更新 2015-06-15 01:00AM: 感谢各位的支持,简历和解决方案接收截止.2015-06-08 08:30AM: 已经收到一些简历和解决方案,正在筛选中.职位仍然开放,欢迎发送简历及解 ...
 - C#4语法
			
在C# 4.0中可以通过委托某个成员的实现来实现一个接口,例如下面的代码: public class Foo : IList { private List _Collection implements ...
 - ASP.NET Web API自身对CORS的支持: CORS授权检验的实施
			
通过<EnableCorsAttribute特性背后的故事>我们知道:由CorsPolicyProvider提供的CorsPolicy表示目标Action采用的资源授权策略,ASP.NET ...
 - ajax的使用:例题、ajax的数据处理
			
需要注意的是,调用的封装的数据库,和jQuery的保存地址 一.注册 (1)写文本框来进行用户名的验证 <input type="text" id="uid&quo ...