专栏首页前端杂货铺jQuery的事件模型

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触发回调主要就是传入参数的处理,执行带有参数的回调。

现附上简单的实现:

  1         // HandlerObject constructor
  2         function Handler(config){
  3             this.handler = config.handler;
  4             this.special = config.special; //特殊的回调,ex. once函数,throggle函数等等,原回调放在此处,handler放包裹后的回调
  5             this.type = config.type;
  6             this.namespace = config.namespace;
  7             this.data = config.data;
  8             this.once = config.once;
  9             this.delay = config.delay;
 10             this.throttle = config.throttle;
 11             this.stop = config.stop;  //  取消默认和冒泡
 12             this.stopBubble = config.stopBubble;
 13             this.preventDefalut = config.preventDefalut;
 14         }
 15         //typeEvents=[handlerObj1,handlerObj2,...]
 16         function execHandlers(el,event,args,context){  // 若args不为空,则为自定义事件出发,trigger
 17             if(el.nodeValue == 3 || el.nodeValue == 8) return;
 18             var elData,events,handlers,typeEvents,ret,
 19                 flag = true;
 20             context = context || el;
 21             //获取缓存对象
 22             elData= S._data(el);
 23             if(!elData || !elData['events'])return;
 24             events = elData['events'];
 25             handlers = elData['handlers'];
 26             if(!events[event.type])return;
 27             typeEvents = events[event.type];
 28 
 29             // 如果其中一个回调执行出错,函数库也不会抛错
 30             for(var i = 0,len=typeEvents.length;i<len;i++){
 31 
 32                 // 捕获错误,如果一个事件绑定多个回调,其中一个回调出错不会影响其他回调执行
 33                 try{
 34                     // 如果设置var isImmediatePropagationStopped,那么执行两件事:
 35                     //  1,停止执行该元素同事件的其他处理函数
 36                     //  2,停止冒泡
 37                     if(event.isImmediatePropagationStopped()) break;
 38                     ret = execHandler(el,event,typeEvents[i],args,context);
 39                     if(ret == false){
 40                         flag = false
 41                     }
 42                 }catch(e){
 43                     setTimeout(function(){
 44                         throw Error(e);   // 异步抛出错误
 45                     },0);
 46 
 47                     if(i < len && i+1 <len){
 48                         i++;
 49                         ret = execHandler(el,event,typeEvents[i],args,context);
 50                         if(ret == false){
 51                             flag = false
 52                         }
 53                     }
 54                 }
 55             }
 56             if(!flag){
 57                 event.preventDefault();
 58                 event.stopPropagation();
 59             }
 60             return;
 61         }
 62         function execHandler(el,event,handlerObj,args,context){
 63             var handler = handlerObj.handler,
 64                 type = event.type,
 65                 special = handlerObj.special,
 66                 stop = handlerObj.stop,
 67                 preventDefault = handlerObj.preventDefalut,
 68                 stopBubble = handlerObj.stopBubble,
 69                 data = handlerObj.data,
 70                 once = handlerObj.once,
 71                 delay = handlerObj.delay,  // 时延
 72                 throttle = handlerObj.throttle; //最小间隔时间
 73             if(handlerObj.type && type !== handlerObj.type) return;
 74 
 75             if(!handler || !S.isFunction(handler))return;
 76 
 77             if(stop){
 78                 event.preventDefalut();
 79                 event.stopPropagation();
 80             }
 81             if(preventDefault){
 82                 event.preventDefalut();
 83             }
 84             if(stopBubble){
 85                 event.stopPropagation();
 86             }
 87 
 88 
 89             if(once){
 90                 var onceHandler = function(event,args){
 91                     return S.once(handler,context,event,args);
 92                 };
 93                 return onceHandler.call(context,event,args);
 94             }
 95             if(delay && S.isNumber(delay)){
 96                 var delayHandler = function(event,args){
 97                     return S.delay(handler,context,delay,event,args);
 98                 }
 99                 return delayHandler.call(context,event,args);
100             }
101             if(throttle && S.isNumber(throttle)){
102                 var throttleHandler = function(event,args){
103                     return S.throttle(handler,context,throttle,event,args);
104                 }
105                 return throttleHandler.call(context,event,args);
106             }
107 
108             if(handler){
109                 return handler.call(context,event,args);
110             }
111             return;
112         }
113 
114         function returnTrue(){
115             return true;
116         }
117         function returnFalse(){
118             return false;
119         }
120         //Event constructor
121         function Event(e){ //传入事件参数
122             this.originalEvent = e;
123             this.isPreventDefault = returnFalse;
124             this.isStopPropagation = returnFalse;
125             this.isImmediatePropagationStopped = returnFalse;
126             var type = e.type;
127             if(/^(\w+)\.(\w+)$/.test(type)){
128                 this.type = RegExp.$1;
129                 this.namespace = RegExp.$2
130             }else{
131                 this.type = type;
132                 this.namespace = '';
133             }
134         }
135 
136         Event.prototype = {
137             preventDefault: function(){
138                 var e = this.originalEvent;
139                 if(e.preventDefalut){
140                     return e.preventDefault();
141                 }
142                 e.returnValue = false;
143                 this.isPreventDefault = returnTrue;
144                 return;
145             },
146             stopPropagation: function(){
147                 var e = this.originalEvent;
148                 if(e.stopPropagation){
149                     return e.stopPropagation();
150                 }
151                 e.stopBubble = true;
152                 this.isStopPropagation = returnTrue;
153                 return;
154             },
155             stopImmediatePropagation: function(){
156                 this.stopPropagation();
157                 this.isImmediatePropagationStopped = returnTrue;
158             }
159         };
160         //事件修复
161         function fixEvent(event){
162             var i, prop, props = [], originalEvent = event.originalEvent;
163 
164             props = props.concat('altKey bubbles button cancelable charCode clientX clientY ctrlKey currentTarget'.split(' '));
165             props = props.concat('data detail eventPhase fromElement handler keyCode layerX layerY metaKey'.split(' '));
166             props = props.concat('newValue offsetX offsetY originalTarget pageX pageY prevValue relatedTarget'.split(' '));
167             props = props.concat('screenX screenY shiftKey target toElement view wheelDelta which'.split(' '));
168             for(i=props.length;--i;){
169                 event[props[i]] = originalEvent[props[i]];
170             }
171 
172             if(!event.target){
173                 event.target = event.srcElement;
174             }
175 
176             if(event.target.nodeType == 3){
177                 event.target = event.target.parentNode;
178             }
179 
180             if(!event.relatedTarget){
181                 event.relatedTarget = event.fromElement === event.target? event.toElement : event.fromElement;
182             }
183 
184             if(!event.which && (event.charCode || event.keyCode)){
185                 event.which = event.charCode ? event.charCode : event.keyCode ? event.keyCode : null;
186             }
187 
188             if(!event.pageX || !event.pageY){
189                 event.pageX = event.clientX + (doc.documentElement && doc.documentElement.scrollLeft || doc.body && doc.body.scrollLeft || 0)
190                 - (doc.documentElement && doc.documentElement.clientLeft || doc.body && doc.body.clientLeft || 0);
191                 event.pageY = event.clientY + (doc.documentElement && doc.documentElement.scrollTop || doc.body && doc.body.scrollTop || 0)
192                     - (doc.documentElement && doc.documentElement.clientTop || doc.body && doc.body.clientTop || 0);
193             }
194 
195             if(!event.which && event.button != undefined){ //ie下 0 无动作, 1 左键 ,2 右键, 4 中间键
196                 event.which = (event.button & 1) ? 1 : (event.button & 2) ? 3 : (event.button & 4) ? 2 : 0;
197             }
198             return event;
199         }
200 
201         function bind(el,type,fn){
202             if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return;
203 
204             var elData= S._data(el),events,handlers,typeEvents;
205             if(!elData) {
206                 S._lockData(el);  //开辟缓存
207                 elData = S._data(el);
208             }
209             if(!elData['events']){
210                 elData['events']  = {};
211             }
212             events = elData['events'];
213             handlers = elData['handlers'];  // 目前先不对其赋值
214             if(!events[type]){
215                 events[type] = [];
216             }
217             typeEvents = events[type];
218 
219             var handlerObj;
220             if(S.isFunction(fn)){
221                 handlerObj = new Handler({handler: fn});
222             }else if(S.isObject(fn)){
223                 handlerObj = new Handler(fn);
224             }else{
225                 return;
226             }
227 
228             handlerObj.handlerHook = function(event,args){  // 函数钩子,用于unbind删除回调函数
229                 event = event || window.event;
230                 var e = new Event(event);
231                 e = fixEvent(e);
232                 execHandlers(el,e,args,el);
233             };
234 
235 
236             if(!typeEvents || !typeEvents.length)
237                 addEvent(el,type,handlerObj.handlerHook);
238 
239             typeEvents.push(handlerObj);
240         }
241 
242         function unbind(el,type,fn){
243             var newEvents = [];
244             if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return;
245 
246             var elData= S._data(el),events,handlers,typeEvents;
247 
248             if(!elData || !elData['events'])return;
249 
250             if(arguments.length == 1){  // 删除该元素所有缓存 事件
251                 for(var i in elData['events']){
252                     if(elData['events'].hasOwnProperty(i)){
253                         for(var j=0,len=elData['events'][i].length;j<len;j++){
254                             removeEvent(el,i,elData['events'][i][j].handlerHook);
255                         }
256                     }
257                 }
258                 S._unData(el);
259             }
260 
261             events = elData['events'][type];
262             newEvents = events.concat();
263 
264             if(arguments.length == 2 && events){
265                 try{
266                     for(var i= 0,len=events.length;i<len;i++){
267                         removeEvent(el,type,events[i].handlerHook);
268                     }
269                 }catch(e){
270                     throw new TypeError('哎呀啊,解除回调出现意外')
271                 }
272 
273                 events = {};
274                 delete elData[type];
275             }
276 
277             if(arguments.length == 3){
278                 for(var i= 0,len=events.length;i<len;i++){
279                     if(events[i].handler === fn){
280                         try{
281                             removeEvent(el,type,events[i].handlerHook);
282                         }catch(e){
283                             throw new TypeError('哎呀啊,解除回调出现意外')
284                         }
285                         newEvents.splice(i,1);
286                     }
287                 }
288             }
289             elData['events'][type] = events = newEvents;
290         }
291 
292         function trigger(el,type,args){
293             if(el.nodeType && el.nodeType == 3 || el.nodeType == 8) return;
294 
295             var elData= S._data(el),events,handlers,typeEvents;
296 
297             if(!elData || !elData['events'] || !elData['events'][type])return;
298             events = elData['events'][type];
299 
300             var handlerObj,event;
301             event = {
302                 target: el,
303                 type: type,
304                 data: args
305             };
306             for(var len=events.length;--len>=0;){
307                 handlerObj = events[len];
308                 handlerObj.handlerHook(event,args);
309             }
310         }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • nodejs中的并发编程

    在nodejs中,如果要实现sleep的功能主要是通过“setTimeout + promise”实现,也可以通过“循环空转”来解决。前者是利用定时器实现任务的...

    欲休
  • ReactJS分析之入口函数render

    前言   在使用React进行构建应用时,我们总会有一个步骤将组建或者虚拟DOM元素渲染到真实的DOM上,将任务交给浏览器,进而进行layout和paint等...

    欲休
  • 使用canvas截图或者改变灰度

    简述 html5新添加的canvas API可以让我们对画布进行开发应用,典型的是可以使用canvas截图或者 手工绘制“迷你图”(即嵌入在文本中的高清小图片)...

    欲休
  • WSAEventSelect模型 ---应用实例,重写TCP服务器实例

    // WSAEvent.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <winsock2.h> #...

    用户1154259
  • 吴恩达机器学习笔记9-代价函数直观理解之二

    Model and Cost Function_Cost Function - Intuition II”

    讲编程的高老师
  • Spring Security OAuth2 实现登录互踢

    一个账号只能一处登录,类似的业务需求在现有后管类系统是非常常见的。 但在原有的 spring security oauth2 令牌方法流程(所谓的登录)无法满足...

    冷冷
  • 逻辑回归中的代价函数—ML Note 36

    上一小节学习了决策边界。我们知道了其实逻辑回归进行分类问题,实质上是我们先有一个模型方程但是不知道方程的参数,我们通过确定参数来确定方程的具体的形式也就是决策边...

    讲编程的高老师
  • Python爬虫爬取知乎小结

    最近学习了一点网络爬虫,并实现了使用Python来爬取知乎的一些功能,这里做一个小的总结。网络爬虫是指通过一定的规则自动的从网上抓取一些信息的程序或脚本。我们知...

    小小科
  • Python爬虫爬取知乎小结

    最近学习了一点网络爬虫,并实现了使用Python来爬取知乎的一些功能,这里做一个小的总结。网络爬虫是指通过一定的规则自动的从网上抓取一些信息的程序或脚本。我们知...

    小小科
  • Python | Python爬虫爬取知乎小结

    http://blog.csdn.net/qq_37267015/article/details/62217899

    用户1634449

扫码关注云+社区

领取腾讯云代金券