1、JS自定义事件
js自定义事件基本思路:定义一个数组,当添加事件的时候,push进去这个事件的处理函数;当执行的时候,从头遍历这个数组中每个事件处理函数,并执行。
当多个事件以及对应数据处理函数添加后,最终会得到一个类似下面的数据结构的对象:
1 listener = {
2 'click': [fun1, fun2],
3 'dbclick': [fun3, fun4],
4 'myevent': [fun5]
5 }
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上进行事件方法扩展是很糟糕的做法。
- 缺少标准;
- 无规律;
- 增加了冲突的可能性;
- 性能问题(IE6-7浏览器下所有的扩展都要通过遍历);
- 浏览器兼容(IE8对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浏览器
document.createEvent() 方法,虽然IE提供了 document.createEventObject() 和 document.fireEvent()
propertychange
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 };
类似jQuery方式的实现
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 };
javascript自定义事件
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>
js自定义事件测试