所谓自定义事件,就是有别于有别于带有浏览器特定行为的事件(类似
click
, mouseover
, submit
, keydown
等事件),事件名称可以随意定义,可以通过特定的方法进行添加,触发以及删除。可以定义一个数组,当添加事件的时候,我们push进去这个事件处理函数;当我们执行的时候,从头遍历这个数组中的每个事件处理函数,并执行。当多个事件以及对应数据处理函数添加后,我们最终会得到一个类似下面数据结构的对象:
_listener = { "click": [func1, func2], "custom": [func3], "defined": [func4, func5, func6] }
实现方式
1、全局变量定义
var _listener = {}; var addEvent = function(type, fn) { // 添加 }; var fireEvent = function(type) { // 触发 }; var removeEvent = function(type, fn) { // 删除 };
2、减少全局变量的方法之一就是使用全局对象(其他如闭包),全局变量属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如_listeners
)不小心在某事件处reset了下,则整个全局的自定义事件都会崩溃。
var Event = { _listeners: {}, // 添加 addEvent: function(type, fn) { if (typeof this._listeners[type] === "undefined") { this._listeners[type] = []; } if (typeof fn === "function") { this._listeners[type].push(fn); } return this; }, // 触发 fireEvent: function(type) { var arrayEvent = this._listeners[type]; if (arrayEvent instanceof Array) { for (var i=0, length=arrayEvent.length; i<length; i+=1) { if (typeof arrayEvent[i] === "function") { arrayEvent[i]({ type: type }); } } } return this; }, // 删除 removeEvent: function(type, fn) { var arrayEvent = this._listeners[type]; if (typeof type === "string" && arrayEvent instanceof Array) { if (typeof fn === "function") { // 清除当前type类型事件下对应fn方法 for (var i=0, length=arrayEvent.length; i<length; i+=1){ if (arrayEvent[i] === fn){ this._listeners[type].splice(i, 1); break; } } } else { // 如果仅仅参数type, 或参数fn邪魔外道,则所有type类型事件清除 delete this._listeners[type]; } } return this; } }; Event.addEvent("alert", function() { alert("弹出!"); }); // 触发自定义alert事件 Event.fireEvent("alert");
3、原型模式实现
var EventTarget = function() { this._listener = {}; }; EventTarget.prototype = { constructor: this, addEvent: function(type, fn) { if (typeof type === "string" && typeof fn === "function") { if (typeof this._listener[type] === "undefined") { this._listener[type] = [fn]; } else { this._listener[type].push(fn); } } return this; }, addEvents: function(obj) { obj = typeof obj === "object"? obj : {}; var type; for (type in obj) { if ( type && typeof obj[type] === "function") { this.addEvent(type, obj[type]); } } return this; }, fireEvent: function(type) { if (type && this._listener[type]) { var events = { type: type, target: this }; for (var length = this._listener[type].length, start=0; start<length; start+=1) { this._listener[type][start].call(this, events); } } return this; }, fireEvents: function(array) { if (array instanceof Array) { for (var i=0, length = array.length; i<length; i+=1) { this.fireEvent(array[i]); } } return this; }, removeEvent: function(type, key) { var listeners = this._listener[type]; if (listeners instanceof Array) { if (typeof key === "function") { for (var i=0, length=listeners.length; i<length; i+=1){ if (listeners[i] === key){ listeners.splice(i, 1); break; } } } else if (key instanceof Array) { for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) { this.removeEvent(type, key[lenkey]); } } else { delete this._listener[type]; } } return this; }, removeEvents: function(params) { if (params instanceof Array) { for (var i=0, length = params.length; i<length; i+=1) { this.removeEvent(params[i]); } } else if (typeof params === "object") { for (var type in params) { this.removeEvent(type, params[type]); } } return this; } }; var myEvents = new EventTarget(); var yourEvents = new EventTarget();
这样,即使myEvents
的事件容器_listener
跛掉,也不会污染yourEvents
中的自定义事件。
var myEvents = new EventTarget(); myEvents.addEvents({ "once": function() { alert("该弹框只会出现一次!"); this.removeEvent("once"); }, "infinity": function() { alert("每次点击页面,该弹框都会出现!"); } }); document.onclick = function(e) { e = e || window.event; var target = e.target || e.srcElement; if (!target || !/input|pre/i.test(target.tagName)) { myEvents.fireEvents(["once", "infinity"]); } };
3、DOM自定义事件
平常所使用的事件基本都是与DOM元素相关的,例如点击按钮,文本输入等,这些为自带浏览器行为事件,而自定义事件与这些行为无关。
element.addEventListener("alert", function() { alert("弹出!"); });
这里的alert
就属于自定义事件,后面的function
就是自定义事件函数。而这个自定义事件是直接绑定在名为element
的DOM元素上的,因此,这个称之为自定义DOM事件。
HTMLElement.prototype.addEvent = function(type, fn, capture) { var el = this; if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); }else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); } };
由于IE6, IE7浏览器的DOM水平较低,无法直接进行扩展,因此,原型扩展的方法在这两个浏览器下是行不通的。要想让这两个浏览器也支持addEvent
方法,只能是页面载入时候遍历所有DOM,然后每个都直接添加addEvent
方法了。
var elAll = document.all, lenAll = elAll.length; for (var iAll=0; iAll<lenAll; iAll+=1) { elAll[iAll].addEvent = function(type, fn) { var el = this; el.attachEvent("on" + type, function(e) { fn.call(el, e); }); }; } document.getElementById("image").addEvent("click", function() { alert("这是:" + this.alt); });
基于DOM扩展缺点有:缺少标准无规律、提高冲突可能性、性能以及浏览器支持。
扩展名字任意命,很有可能就会与未来DOM浏览器本身支持的方法相互冲突;扩展无规律,很有可能出现A和B同名不同功能的扩展而造成冲突;IE6-7浏览器下所有扩展都要通过遍历支持,其性能开销可想而知;另外IE8对DOM扩展的支持并不完整,例如其支持
Element.prototype
,却没有HTMLElement.prototype
4、伪DOM自定义事件
var $ = function(el) { return new _$(el); }; var _$ = function(el) { this.el = el; }; _$.prototype = { constructor: this, addEvent: function(){}, fireEvent: function() {}, removeEvent: function() {} }
可以使用类似$(dom).addEvent()
的语法为元素添加事件了(包括不包含浏览器行为的自定义事件)
addEvent: function(type, fn, capture) { var el = this.el; if (window.addEventListener) { el.addEventListener(type, fn, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, fn); } return this; }
自定义事件添加容易,但自定义事件与浏览器行为无关,同时浏览器没有直接的触发事件的方法。
1. 对于标准浏览器
$(dom).addEvent("alert", function() { alert("do something"); }); // 创建 var evt = document.createEvent("HTMLEvents"); // 初始化 evt.initEvent("alert", false, false); // 触发, 即弹出文字 dom.dispatchEvent(evt);
createEvent()
方法返回新创建的Event
对象,支持一个参数,表示事件类型
initEvent()
方法用于初始化通过DocumentEvent
接口创建的Event
的值。支持三个参数:initEvent(eventName, canBubble, preventDefault)
. 分别表示事件名称,是否可以冒泡,是否阻止事件的默认操作。dispatchEvent()
就是触发执行了,dom.dispatchEvent(eventObject)
, 参数eventObject
表示事件对象,是createEvent()
方法返回的创建的Event
对象。addEventListener('dataavailable', handler, false);dataavailable 新事件名、handler 触发的函数、false 是否是扑获事件模型
2. 对于IE浏览器
由于向下很多版本的浏览器都不支持
document.createEvent()
方法,因此我们需要另辟蹊径(据说IE有document.createEventObject()
和event.fireEvent()
方法,但是不支持自定义事件) IE浏览器有不少自给自足的东西,例如下面要说的这个
"propertychange"
事件,属性改变即触发事件。例如文本框value
值改变,或是元素id
改变,或是绑定的事件改变等等。当我们添加自定义事件的时候,顺便给元素添加一个自定义属性即可
dom.evtAlert = "2015-09-017"; dom.attachEvent("onpropertychange", function(e) { if (e.propertyName == "evtAlert") { fn.call(this); } }); //触发自定义事件的时候,只要修改DOM上自定义的evtAlert属性 dom.evtAlert = Math.random(); // 值变成随机数 //自定义事件的删除 dom.detachEvent("onpropertychange", evt);
var $ = function(el) { return new _$(el); }; var _$ = function(el) { this.el = (el && el.nodeType == 1)? el: document; }; _$.prototype = { constructor: this, addEvent: function(type, fn, capture) { var el = this.el; if (window.addEventListener) { el.addEventListener(type, fn, capture); var ev = document.createEvent("HTMLEvents"); ev.initEvent(type, capture || false, false); if (!el["ev" + type]) { el["ev" + type] = ev; } } else if (window.attachEvent) { el.attachEvent("on" + type, fn); if (isNaN(el["cu" + type])) { // 自定义属性 el["cu" + type] = 0; } var fnEv = function(event) { if (event.propertyName == "cu" + type) { fn.call(el); } }; el.attachEvent("onpropertychange", fnEv); if (!el["ev" + type]) { el["ev" + type] = [fnEv]; } else { el["ev" + type].push(fnEv); } } return this; }, fireEvent: function(type) { var el = this.el; if (typeof type === "string") { if (document.dispatchEvent) { if (el["ev" + type]) { el.dispatchEvent(el["ev" + type]); } } else if (document.attachEvent) { el["cu" + type]++; } } return this; }, removeEvent: function(type, fn, capture) { var el = this.el; if (window.removeEventListener) { el.removeEventListener(type, fn, capture || false); } else if (document.attachEvent) { el.detachEvent("on" + type, fn); var arrEv = el["ev" + type]; if (arrEv instanceof Array) { for (var i=0; i<arrEv.length; i+=1) { el.detachEvent("onpropertychange", arrEv[i]); } } } return this; } }; $(elImage) .addEvent("click", funClick); .addEvent("alert", funAlert1) .addEvent("alert", funAlert2); $(elImage).fireEvent("alert");
KISSY中自定义事件