标签:
1、JS自定义事件
js自定义事件基本思路:定义一个数组,当添加事件的时候,push进去这个事件的处理函数;当执行的时候,从头遍历这个数组中每个事件处理函数,并执行。
当多个事件以及对应数据处理函数添加后,最终会得到一个类似下面的数据结构的对象:
1 listener = { 2 ‘click‘: [fun1, fun2], 3 ‘dbclick‘: [fun3, fun4], 4 ‘myevent‘: [fun5] 5 }
因此,如果脱离了DOM,纯粹在数据层自定义事件的话,我们只要构建、遍历和删除 listener 对象即可。
1.1 函数式实现
函数式实现就是定义添加事件、触发事件和删除事件的三个方法,在需要的地方直接调用这些方法。
1 var listener = {}; 2 var addEvent = function(type, fn) { 3 // 添加 4 }; 5 var fireEvent = function(type) { 6 // 触发 7 }; 8 var removeEvent = function(type, fn) { 9 // 删除 10 };
这种实现方式的缺点是:过多使用全局变量,方法无级联。
1.2 字面量实现
为了减少全局变量,我们可以采用对象字面量的方式。
完整的字面量实现方式的代码如下:
1 var Event = { 2 listener: {}, 3 addEvent: function(type, fn) { 4 if(typeof this.listener[type] === ‘undefined‘) { 5 this.listener[type] = []; 6 } 7 if(typeof fn === ‘function‘) { 8 this.listener[type].push(fn); 9 } 10 return this; 11 }, 12 triggerEvent: function(type) { 13 var arrayEvent = this.listener[type]; 14 if(arrayEvent instanceof Array) { 15 for(var i = 0, len = arrayEvent.length; i < len; i++) { 16 if(typeof arrayEvent[i] === ‘function‘) { 17 arrayEvent[i]({ 18 type: type 19 }); 20 } 21 } 22 } 23 return this; 24 }, 25 removeEvent: function(type, fn) { 26 var arrayEvent = this.listener[type]; 27 if(typeof type === ‘string‘ && arrayEvent instanceof Array) { 28 if(typeof fn === ‘function‘) { 29 for(var i = 0, len = arrayEvent.length; i < len; i++) { 30 if(arrayEvent[i] === fn) { 31 this.listener[type].splice(i, 1); 32 break; 33 } 34 } 35 } else { 36 delete this.listener[type]; 37 } 38 } 39 return this; 40 } 41 };
字面量实现方式虽然减少了全局变量,但是其属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如 listener )不小心在某事件处被重置了,则整个全局的自定义事件都会有问题。因此,需要进一步的改进,如使用原型链的方式,让继承的属性即使出现问题也不会影响全局。
1.3 原型模式实现
相比上面,增加了 addEvents、triggerEvents、removeEvents 多事件绑定、执行和删除方法,代码如下:
1 var EventTarget = function() { 2 this.listener = {}; 3 }; 4 5 EventTarget.prototype = { 6 constructor: this, 7 addEvent: function(type, fn) { 8 if(typeof type === ‘string‘ && typeof fn === ‘function‘) { 9 if(typeof this.listener[type] ===‘undefined‘) { 10 this.listener[type] = [fn]; 11 } else { 12 this.listener[type].push(fn); 13 } 14 } 15 return this; 16 }, 17 addEvents: function(obj) { 18 obj = typeof obj === ‘object‘ ? obj : {}; 19 for(var type in obj) { 20 if(type && typeof obj[type] === ‘function‘) { 21 this.addEvent(type, obj[type]); 22 } 23 } 24 return this; 25 }, 26 triggerEvent: function(type) { 27 if(type && this.listener[type]) { 28 var events = { 29 type: type, 30 target: this 31 }; 32 for(var len = this.listener[type].length, start = 0; start < len; start += 1) { 33 this.listener[type][start].call(this, events); 34 } 35 } 36 return this; 37 }, 38 triggerEvents: function(array) { 39 if(array instanceof Array) { 40 for(var i = 0, len = array.length; i < len; i += 1) { 41 this.triggerEvent(array[i]); 42 } 43 } 44 return this; 45 }, 46 removeEvent: function(type, callback) { 47 var listeners = this.listener[type]; 48 if(listeners instanceof Array) { 49 if(typeof callback === ‘function‘) { 50 for(var i = 0, len = listeners.length; i < len; i += 1) { 51 if(listeners[i] === callback) { 52 listeners.splice(i, 1); 53 break; 54 } 55 } 56 } else if(callback instanceof Array) { 57 for(var lis = 0, keyLen = callback.length; lis < keyLen; lis += 1) { 58 this.removeEvent(type, callback[lis]); 59 } 60 } else { 61 delete this.listener[type]; 62 } 63 } 64 return this; 65 }, 66 removeEvents: function(types) { 67 if(types instanceof Array) { 68 for(var i =0, len = types.length; i < len; i += 1) { 69 this.removeEvent(types[i]); 70 } 71 } else if(typeof types === ‘object‘) { 72 for(var type in types) { 73 this.removeEvent(type, types[type]); 74 } 75 } 76 return this; 77 } 78 };
需要实现自定义功能时候,先 new 构造下:
1 var myEvent = new EventTarget(); 2 var myEvent1 = new EventTarget(); 3 var myEvent2 = new EventTarget();
这样,即使 myEvent 的事件容器listener被重置或修改了,也不会影响到myEvent1中自定义事件。
2、DOM自定义事件
直接在DOM上进行事件方法扩展是很糟糕的做法。
3、类似jQuery的DOM自定义事件
3.1 自定义事件的添加
根据浏览器的支持情况,分别使用addEventListener和attachEvent方法添加事件。
1 addEvent: function(type, fn, capture) { 2 var el = this.el; 3 if (window.addEventListener) { 4 el.addEventListener(type, fn, capture); 5 } else if (window.attachEvent) { 6 el.attachEvent("on" + type, fn); 7 } 8 return this; 9 }
3.2 自定义事件的触发
由于不同浏览器处理方式不同,尤其是针对IE6/7等比较古老的浏览器。
3.2.1 标准浏览器
标准浏览器可以使用element.dispatchEvent()方法,在调用该方法之前,需要先创建和初始化。
1 document.createEvent() 2 event.initEvent() 3 element.dispatchEvent()
createEvent() 方法返回新创建的 Event 对象,支持一个参数,表示事件类型,具体见下表:
参数 | 事件接口 | 初始化方法 |
HTMLEvents | HTMLEvent | initEvent() |
MouseEvents | MouseEvent | initMouseEvent() |
UIEvents | UIEvent | initUIEvent() |
initEvent() 方法用于初始化通过 DocumentEvent 接口创建的 Event 的值,支持三个参数: initEvent(eventName, canBubble, preventDefault) 。分别表示事件名称,是否可以冒泡,是否阻止事件的默认操作。
dispatchEvent(event) 是触发执行,参数event表示事件对象,是createEvent()方法返回的Event对象。
3.2.2 IE浏览器
IE很多版本都不支持 document.createEvent() 方法,虽然IE提供了 document.createEventObject() 和 document.fireEvent() 方法,但其不支持自定义事件。
IE中 propertychange 事件,是在属性改变的时候触发事件。
IE中实现自定义事件的基本思路:当添加自定义事件的时候,同时给元素添加一个自定义属性。如我们添加自定义事件"moveClick",同时对元素添加"moveProperty"属性,再把自定义的事件添加到propertychange事件中:
1 dom.attachEvent("onpropertychange", function(e) { 2 if (e.propertyName == "moveProperty") { 3 fn.call(this); 4 } 5 });
当需要触发自定义事件时,修改DOM元素上自定义的"moveProperty"属性即可。
3.3 自定义事件的删除
对于删除自定义事件,各个浏览器都提供了删除的方法,如 removeEventListener 和 detachEvent 。在IE浏览器中,还需要删除额外增加的 onpropertychange 事件:
dom.detachEvent(‘onpropertychange‘, evt);
3.4 完整的实现代码
结合上述,可以得到完整的实现代码:
1 var $$ = function(element) { 2 return new _$(element); 3 }; 4 5 var _$ = function(element) { 6 this.element = (element && element.nodeType ===1 ) ? element : document; 7 }; 8 9 _$.prototype = { 10 constructor: this, 11 addEvent: function(type, fn, capture) { 12 var element = this.element; 13 if(window.addEventListener) { 14 element.addEventListener(type, fn, capture); 15 16 var evt = document.createEvent(‘HTMLEvents‘); 17 evt.initEvent(type, capture || false, false); 18 //在元素上存储创建的事件,方便自定义触发 19 if(!element[‘ev‘ + type]) { 20 element[‘ev‘ + type] = evt; 21 } 22 } else if(window.attachEvent) { 23 element.attachEvent(‘on‘ + type, fn); 24 if(isNaN(element[‘cu‘ + type])) { 25 //自定义属性,触发事件用 26 element[‘cu‘ + type] = 0; 27 } 28 29 var fnEv = function(event) { 30 if(event.propertyName === ‘cu‘ + type) { 31 fn.call(element); 32 } 33 }; 34 35 el.attachEvent(‘onpropertychange‘, fnEv); 36 37 //在元素上存储帮的propertychange事件,方便删除 38 if(!element[‘ev‘ + type]) { 39 element[‘ev‘ + type] = [fnEv]; 40 } else { 41 element[‘ev‘ + type].push(fnEv); 42 } 43 } 44 return this; 45 }, 46 triggerEvent: function(type) { 47 var element = this.element; 48 if(typeof type === ‘string‘) { 49 if(document.dispatchEvent) { 50 if(element[‘ev‘ + type]) { 51 element.dispatchEvent(element[‘ev‘ + type]); 52 } else if(document.attachEvent) { 53 //改变对应自定义属性,触发自定义事件 54 element[‘cu‘ + type]++; 55 } 56 } 57 } 58 return this; 59 }, 60 removeEvent: function(type, fn, capture) { 61 var element = this.element; 62 if(window.removeEventListener) { 63 element.removeEventListener(type, fn, capture || false); 64 } else if(window.detachEvent) { 65 element.detachEvent(‘on‘ + type, fn); 66 var arrEv = element[‘ev‘ + type]; 67 if(arrEv instanceof Array) { 68 for(var i = 0, len = arrEv.length; i < len; i += 1) { 69 //删除该方法名下所有绑定的propertychange事件 70 element.detachEvent(‘onpropertychange‘, arrEv[i]); 71 } 72 } 73 } 74 return this; 75 } 76 };
4 javascript自定义事件完整代码示例
4.1 javascript部分完整代码
1 /** 2 * @description 包含事件监听、移动和模拟事件触发的事件机制,支持链式调用 3 */ 4 //插件方式 5 (function(window, undefined) { 6 var Ev = window.Ev = window.$ = function(element) { 7 return new Ev.fn.init(element); 8 }; 9 10 /*EV对象构建*/ 11 Ev.fn = Ev.prototype = { 12 init: function(element) { 13 this.element = (element && element.nodeType === 1) ? element : document; 14 }, 15 16 /* 17 添加事件监听 18 19 */ 20 add: function(type, callback) { 21 var that = this; 22 if(that.element.addEventListener) { 23 //支持Modern和IE9浏览器 24 that.element.addEventListener(type, callback, false); 25 } else if(that.element.attachEvent) { 26 //支持IE5+ 27 if(type.indexOf(‘custom‘) !== -1) { 28 if(isNaN(that.element[type])) { 29 that.element[type] = 0; 30 } 31 32 var fnEv = function(event) { 33 event = event ? event : window.event; 34 if(event.propertyName === type) { 35 callback.call(that.element); 36 } 37 }; 38 that.element.attachEvent(‘onpropertychange‘, fnEv); 39 40 //在元素上存储绑定的propertychange的回调,方便移除事件绑定 41 if(!that.element[‘callback‘ + callback]) { 42 that.element[‘callback‘ + callback] = fnEv; 43 } 44 } else { 45 that.element[‘on‘ + type] = callback; 46 } 47 } else { 48 //其他浏览器 49 that.element[‘on‘ + type] = callback; 50 } 51 return that; 52 }, 53 remove: function(type, callback) { 54 var that = this; 55 if(that.element.removeEventListener) { 56 that.element.removeEventListener(type, callback, false); 57 } else if(that.element.detachEvent) { 58 //自定义事件处理,支持IE5+ 59 if(type.indexOf(‘custom‘) !== -1) { 60 //移除对相应的自定义属性的监听 61 that.element.detachEvent(‘onpropertychange‘, that.element[‘callback‘ + callback]); 62 that.element[‘callback‘ + callback] = null; 63 } 64 } else { 65 that.element.detachEvent(‘on‘ + type, callback); 66 } 67 return that; 68 }, 69 /** 70 模拟触发事件 71 */ 72 trigger: function(type) { 73 var that = this; 74 try { 75 //现代浏览器 76 if(that.element.dispatchEvent) { 77 //创建事件 78 var evt = document.createEvent(‘Event‘); 79 //定义事件类型 80 evt.initEvent(type, true, true); 81 //触发事件 82 that.element.dispatchEvent(evt); 83 //IE 84 } else if(that.element.fireEvent) { 85 if(type.indexOf(‘custom‘) != -1) { 86 that.element[type] ++; 87 } else { 88 that.element.fireEvent(‘on‘ + type); 89 } 90 } 91 } catch(e) { 92 93 } 94 return that; 95 } 96 } 97 Ev.fn.init.prototype = Ev.fn; 98 })(window); 99 100 //对象字面量方式 101 var Event = { 102 listener: {}, 103 addEvent: function(type, fn) { 104 if(typeof this.listener[type] === ‘undefined‘) { 105 this.listener[type] = []; 106 } 107 if(typeof fn === ‘function‘) { 108 this.listener[type].push(fn); 109 } 110 return this; 111 }, 112 triggerEvent: function(type) { 113 var arrayEvent = this.listener[type]; 114 if(arrayEvent instanceof Array) { 115 for(var i = 0, len = arrayEvent.length; i < len; i++) { 116 if(typeof arrayEvent[i] === ‘function‘) { 117 arrayEvent[i]({ 118 type: type 119 }); 120 } 121 } 122 } 123 return this; 124 }, 125 removeEvent: function(type, fn) { 126 var arrayEvent = this.listener[type]; 127 if(typeof type === ‘string‘ && arrayEvent instanceof Array) { 128 if(typeof fn === ‘function‘) { 129 for(var i = 0, len = arrayEvent.length; i < len; i++) { 130 if(arrayEvent[i] === fn) { 131 this.listener[type].splice(i, 1); 132 break; 133 } 134 } 135 } else { 136 delete this.listener[type]; 137 } 138 } 139 return this; 140 } 141 }; 142 143 //原型方式 144 var EventTarget = function() { 145 this.listener = {}; 146 }; 147 148 EventTarget.prototype = { 149 constructor: this, 150 addEvent: function(type, fn) { 151 if(typeof type === ‘string‘ && typeof fn === ‘function‘) { 152 if(typeof this.listener[type] ===‘undefined‘) { 153 this.listener[type] = [fn]; 154 } else { 155 this.listener[type].push(fn); 156 } 157 } 158 return this; 159 }, 160 addEvents: function(obj) { 161 obj = typeof obj === ‘object‘ ? obj : {}; 162 for(var type in obj) { 163 if(type && typeof obj[type] === ‘function‘) { 164 this.addEvent(type, obj[type]); 165 } 166 } 167 return this; 168 }, 169 triggerEvent: function(type) { 170 if(type && this.listener[type]) { 171 var events = { 172 type: type, 173 target: this 174 }; 175 for(var len = this.listener[type].length, start = 0; start < len; start += 1) { 176 this.listener[type][start].call(this, events); 177 } 178 } 179 return this; 180 }, 181 triggerEvents: function(array) { 182 if(array instanceof Array) { 183 for(var i = 0, len = array.length; i < len; i += 1) { 184 this.triggerEvent(array[i]); 185 } 186 } 187 return this; 188 }, 189 removeEvent: function(type, callback) { 190 var listeners = this.listener[type]; 191 if(listeners instanceof Array) { 192 if(typeof callback === ‘function‘) { 193 for(var i = 0, len = listeners.length; i < len; i += 1) { 194 if(listeners[i] === callback) { 195 listeners.splice(i, 1); 196 break; 197 } 198 } 199 } else if(callback instanceof Array) { 200 for(var lis = 0, keyLen = callback.length; lis < keyLen; lis += 1) { 201 this.removeEvent(type, callback[lis]); 202 } 203 } else { 204 delete this.listener[type]; 205 } 206 } 207 return this; 208 }, 209 removeEvents: function(types) { 210 if(types instanceof Array) { 211 for(var i =0, len = types.length; i < len; i += 1) { 212 this.removeEvent(types[i]); 213 } 214 } else if(typeof types === ‘object‘) { 215 for(var type in types) { 216 this.removeEvent(type, types[type]); 217 } 218 } 219 return this; 220 } 221 }; 222 223 //DOM/伪DOM方式 224 var $$ = function(element) { 225 return new _$(element); 226 }; 227 228 var _$ = function(element) { 229 this.element = (element && element.nodeType ===1 ) ? element : document; 230 }; 231 232 _$.prototype = { 233 constructor: this, 234 addEvent: function(type, fn, capture) { 235 var element = this.element; 236 if(window.addEventListener) { 237 element.addEventListener(type, fn, capture); 238 239 var evt = document.createEvent(‘HTMLEvents‘); 240 evt.initEvent(type, capture || false, false); 241 //在元素上存储创建的事件,方便自定义触发 242 if(!element[‘ev‘ + type]) { 243 element[‘ev‘ + type] = evt; 244 } 245 } else if(window.attachEvent) { 246 element.attachEvent(‘on‘ + type, fn); 247 if(isNaN(element[‘cu‘ + type])) { 248 //自定义属性,触发事件用 249 element[‘cu‘ + type] = 0; 250 } 251 252 var fnEv = function(event) { 253 if(event.propertyName === ‘cu‘ + type) { 254 fn.call(element); 255 } 256 }; 257 258 el.attachEvent(‘onpropertychange‘, fnEv); 259 260 //在元素上存储帮的propertychange事件,方便删除 261 if(!element[‘ev‘ + type]) { 262 element[‘ev‘ + type] = [fnEv]; 263 } else { 264 element[‘ev‘ + type].push(fnEv); 265 } 266 } 267 return this; 268 }, 269 triggerEvent: function(type) { 270 var element = this.element; 271 if(typeof type === ‘string‘) { 272 if(document.dispatchEvent) { 273 if(element[‘ev‘ + type]) { 274 element.dispatchEvent(element[‘ev‘ + type]); 275 } else if(document.attachEvent) { 276 //改变对应自定义属性,触发自定义事件 277 element[‘cu‘ + type]++; 278 } 279 } 280 } 281 return this; 282 }, 283 removeEvent: function(type, fn, capture) { 284 var element = this.element; 285 if(window.removeEventListener) { 286 element.removeEventListener(type, fn, capture || false); 287 } else if(window.detachEvent) { 288 element.detachEvent(‘on‘ + type, fn); 289 var arrEv = element[‘ev‘ + type]; 290 if(arrEv instanceof Array) { 291 for(var i = 0, len = arrEv.length; i < len; i += 1) { 292 //删除该方法名下所有绑定的propertychange事件 293 element.detachEvent(‘onpropertychange‘, arrEv[i]); 294 } 295 } 296 } 297 return this; 298 } 299 };
4.2 测试代码
1 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 2 <body> 3 <button id="mybutton">Click</button> 4 <div style="background-color: darkgray; width:500px;height: 400px;" id="result"></div> 5 6 <p> 7 <input type="button" id="button1" value="触发alert事件" /> 8 <input type="button" id="button2" value="清除第一个alert" /> 9 <input type="button" id="button3" value="清除所有alert" /> 10 </p> 11 12 <p> 13 <input type="button" id="button4" value="触发基于原型的自定义事件"/> 14 <input type="button" id="button5" value="触发基于[伪]DOM的自定义事件"/> 15 <input type="button" id="button6" value="删除基于[伪]DOM的自定义事件"/> 16 </p> 17 <script type="text/javascript" src="event.js"></script> 18 <script type="text/javascript"> 19 var btn = document.getElementById(‘mybutton‘), 20 result = document.getElementById(‘result‘); 21 function triggerDiv1() { 22 result.innerHTML += ‘触发了一次插件方式自定义事件‘ + ‘<br/>‘; 23 } 24 25 function triggerDiv2() { 26 result.innerHTML += ‘再次触发了插件方式自定义事件‘ + ‘<br/>‘; 27 } 28 29 //封装 30 div = $(btn); 31 32 //绑定两次回调,支持链式 33 div.add(‘customTrigger‘, triggerDiv1).add(‘customTrigger‘, triggerDiv2); 34 35 btn.onclick = function() { 36 div.trigger(‘customTrigger‘); 37 } 38 39 var btn1 = document.getElementById(‘button1‘), 40 btn2 = document.getElementById(‘button2‘), 41 btn3 = document.getElementById(‘button3‘), 42 fun1, fun2; 43 //添加自定义事件 44 Event.addEvent(‘objEvent‘, fun1 = function() { 45 result.innerHTML += ‘触发了一次字面量方式的自定义事件‘ + ‘<br/>‘; 46 }).addEvent(‘objEvent‘, fun2 = function() { 47 result.innerHTML += ‘再次触发了字面量方式的自定义事件‘ + ‘<br/>‘; 48 }); 49 50 btn1.onclick = function() { 51 Event.triggerEvent(‘objEvent‘); 52 } 53 54 btn2.onclick = function() { 55 Event.removeEvent(‘objEvent‘, fun1); 56 result.innerHTML += ‘移除了字面量方式的自定义事件fun1‘ + ‘<br/>‘; 57 } 58 59 btn3.onclick = function() { 60 Event.removeEvent(‘objEvent‘); 61 result.innerHTML += ‘移除了所有字面量方式的自定义事件‘ + ‘<br/>‘; 62 } 63 64 var btn4 = document.getElementById(‘button4‘), 65 proEvt = new EventTarget(), 66 onceFun; 67 proEvt.addEvents({ 68 ‘once‘: function() { 69 result.innerHTML += ‘once自定义事件只会触发一次‘ + ‘<br/>‘; 70 this.removeEvent(‘once‘); 71 }, 72 ‘every‘: function() { 73 result.innerHTML += ‘every自定义事件每次都会触发‘ + ‘<br/>‘; 74 } 75 }); 76 btn4.onclick = function() { 77 proEvt.triggerEvents([‘once‘, ‘every‘]); 78 } 79 80 var fnClick = function(e) { 81 e = e || window.event; 82 var target = e.target || e.srcElement; 83 if(target.nodeType === 1) { 84 result.innerHTML += ‘点击类型:‘ + e.type + ‘<br/>‘; 85 $$(target).triggerEvent(‘show_result‘); 86 }; 87 }, 88 domFun1 = function() { 89 result.innerHTML += ‘DOM自定义事件<br/>‘; 90 }, 91 domFun2 = function() { 92 result.innerHTML += ‘DOM自定义事件Again<br/>‘; 93 } 94 95 var btn5 = document.getElementById(‘button5‘); 96 $$(btn5).addEvent(‘click‘, fnClick).addEvent(‘show_result‘, domFun1).addEvent(‘show_result‘, domFun2); 97 98 var btn6 = document.getElementById(‘button6‘); 99 $$(btn6).addEvent(‘click‘, function() { 100 $$(btn5).removeEvent(‘show_result‘, domFun1).removeEvent(‘show_result‘, domFun2); 101 result.innerHTML += ‘DOM自定义事件清除完成<br/>‘; 102 }); 103 </script> 104 </body>
参考文章:http://www.zhangxinxu.com/wordpress/?p=2330
标签:
原文地址:http://www.cnblogs.com/baizn/p/4586343.html