前几天自己着重读了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 }