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 }