前几天自己着重读了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的事件模型的更多相关文章

  1. jQuery (01) 浏览器的事件模型

    浏览器的事件模型 由网景公司引入的 DOM0 级事件模型 把事件处理程序绑定到 DOM 元素的属性上: ele.onclick(); ele.onDOMContentLoad(); ele.onloa ...

  2. Javascript事件模型系列(三)jQuery中的事件监听方式及异同点

    作为全球最知名的js框架之一,jQuery的火热程度堪称无与伦比,简单易学的API再加丰富的插件,几乎是每个前端程序员的必修课.从读<锋利的jQuery>开始,到现在使用jQuery有一年 ...

  3. javascript中0级DOM和2级DOM事件模型浅析 分类: C1_HTML/JS/JQUERY 2014-08-06 15:22 253人阅读 评论(0) 收藏

    Javascript程序使用的是事件驱动的设计模式,为一个元素添加事件监听函数,当这个元素的相应事件被触发那么其添加的事件监听函数就被调用: <input type="button&q ...

  4. Javascript事件模型系列(一)事件及事件的三种模型

    一.开篇 在学习javascript之初,就在网上看过不少介绍javascript事件的文章,毕竟是js基础中的基础,文章零零散散有不少,但遗憾的是没有看到比较全面的系列文章.犹记得去年这个时候,参加 ...

  5. JS的事件模型

    之前对事件模型还是比较清楚的,许多概念都清晰映射在脑海中.工作之后,一方面使用的局限性,二是习惯于用框架中的各种事件监听方式,简单即方便,久而久之,事件的一些概念开始淡出记忆中,就像我现在已经开始淡忘 ...

  6. JavaScript DOM事件模型

    早期由于浏览器厂商对于浏览器市场的争夺,各家浏览器厂商对同一功能的JavaScript的实现都不进相同,本节内容介绍JavaScript的DOM事件模型及事件处理程序的分类. 1.DOM事件模型.DO ...

  7. js 事件模型

    说到事件,就要追溯到网景与微软的“浏览器大战”了.当时,事件模型还没有标准,两家公司的实现就是事实标准.网景在Navigator中实现了“事件捕获”的事件系统,而微软则在IE中实现了一个基本上相反的事 ...

  8. ExtJS框架基础:事件模型及其常用功能

    前言 工作中用ExtJS有一段时间了,Ext丰富的UI组件大大的提高了开发B/S应用的效率.虽然近期工作中天天都用到ExtJS,但很少对ExtJS框架原理性的东西进行过深入学习,这两天花了些时间学习了 ...

  9. 【JQuery】事件冒泡及使用jQuery阻止

    (1)什么是事件起泡 首先你要明白一点,当一个事件发生的时候,该事件总是有一个事件源,即引发这个事件的对象,一个事件不能凭空产生,这就是事件的发生. 当事件发生后,这个事件就要开始传播.为什么要传播呢 ...

随机推荐

  1. 页面制作部分之PS切图

    页面制作部分之PS切图 <--本标签下,通过页面制作.页面架构.javascript程序设计.DOM编程艺术.产品前端架构五部分来分享总结笔记,总结笔记会陆续分享--> 网页设计在技术层面 ...

  2. hadoop+tachyon+spark的zybo cluster集群综合配置

    1.zybo cluster 架构简述: 1.1 zybo cluster 包含5块zybo 开发板组成一个集群,zybo的boot文件为digilent zybo reference design提 ...

  3. Nginx 配置从零开始

    作为一个 nginx 的初学者记录一下从零起步的点滴. 基本概念 Nginx 最常的用途是提供反向代理服务,那么什么反向代理呢?正向代理相信很多大陆同胞都在这片神奇的土地上用过了,原理大致如下图: 代 ...

  4. 各种android应用模仿源码

    V2EX的非官方Android客户端,极力遵循Material Design风格 下载 湘潭大学三翼校园"四季电台" Android客户端   下载   高仿煎蛋客户端    下载 ...

  5. 多位数每一位个系数:个位num%10;十位num/10%10.......

    请输出满足这样条件的五位数. 个位=万位 十位=千位 个位+十位+千位+万位=百位 思路: 1.定义一个要操作的五位数变量num 2.求出每一位个系数 个:num%10 十:num/10%10 百:n ...

  6. IEEE/ACM ASONAM 2014 Industry Track Call for Papers

    IEEE/ACM International Conference on Advances in Social Network Analysis and Mining (ASONAM) 2014 In ...

  7. iphone使用mac上的SOCKS代理

    Step 1. Make sure the SOCKS tunnel on your work computer allows LAN connections so your iPhone/iPod ...

  8. AWS 免费套餐

    AWS 免费套餐 转载自:https://aws.amazon.com/cn/free/?sc_channel=PS&sc_campaign=acquisition_CN&sc_pub ...

  9. 通过Javascript得到URL中的参数(query string)

    我们知道,"GET"请求中,通常把参数放在URL后面,比如这样http://www.cnblogs.com/season-huang/index?param=yes&art ...

  10. c#实现redis客户端(一)

    最近项目使用中要改造redis客户端,看了下文档,总结分享一下. 阅读目录: 协议规范 基础通信 状态命令 set.get命令 管道.事务 总结 协议规范 redis允许客户端以TCP方式连接,默认6 ...