背景
javascript中使用addEventListener()或attachEvent()绑定事件时会有几个小问题:
一、使用addEventListener()或attachEvent()添加的匿名函数无法移除。
var oBtn = document.getElementById('btn'); oBtn.addEventListener('click',function(){ alert('button is clicked') },false) oBtn.reomveEventListener('click',function(){ alert('button is clicked') },false) //oBtn上的事件无法移除,因为传入的是一个匿名函数
var oBtn = document.getElementById('btn'); oBtn.attachEvent('onclick',function(){ alert(1) }) oBtn.attachEvent('onclick',function(){ alert(2) }) oBtn.attachEvent('onclick',function(){ alert(3) }) //ie9+ 下执行顺序1、2、3 //ie6-ie8下执行顺序3、2、1
解决问题
我想写一个跨浏览器的事件绑定模块,这样以后可以复用,同时我想解决上诉问题。JQuery内部使用事件队列和数据缓存机制解决此问题,看了下相关源码,实在复杂,自个试了一些方法,勉强实现。贴段代码,原来是用面向对象写的,不想让人看得很复杂,所有改成函数来组织。
/*绑定事件的接口 * *@param {dom-DOM}和{type-string}和{fn-function} 可选参数{fnName-string} *@execute 创建事件队列,添加到DOM对象属性上, 将事件处理程序(函数)加入事件队列 可为事件处理程序添加一个标识符,用于删除指定事件处理程序 */ function bind(dom,type,fn,fnName){ dom.eventQueue = dom.eventQueue || {}; dom.eventQueue[type] = dom.eventQueue[type] || {}; dom.handler = dom.handler || {}; if (!fnName) { var index = queueLength(dom,type); dom.eventQueue[type]['fnQueue'+index] = fn; } else { dom.eventQueue[type][fnName] = fn; }; if (!dom.handler[type]) bindEvent(dom,type); }; /*绑定事件 * *@param {dom-DOM}和{type-string} *@execute 只绑定一次事件,handler用于遍历执行事件队列中的事件处理程序(函数) *@caller bind() */ function bindEvent(dom,type){ dom.handler[type] = function(){ for(var guid in dom.eventQueue[type]){ dom.eventQueue[type][guid].call(dom); } }; if (window.addEventListener) { dom.addEventListener(type,dom.handler[type],false); } else { dom.attachEvent('on'+type,dom.handler[type]); }; }; /*移除事件的接口 * *@param {dom-DOM}和{type-string} 可选参数{fnName-function} *@execute 如果没有标识符,则执行unBindEvent() 如果有标识符,则删除指定事件处理程序,如果事件队列长度为0,执行unBindEvent() */ function unBind(dom,type,fnName){ var hasQueue = dom.eventQueue && dom.eventQueue[type]; if (!hasQueue) return; if (!fnName) { unBindEvent(dom,type) } else { delete dom.eventQueue[type][fnName]; if (queueLength(dom,type) == 0) unBindEvent(dom,type); }; }; /*移除事件 * *@param {dom-DOM}和{type-string} *@execute 移除绑定的事件处理程序handler,并清空事件队列 *@caller unBind() */ function unBindEvent(dom,type){ if (window.removeEventListener) { dom.removeEventListener(type,dom.handler[type]) } else { dom.detachEvent(type,dom.handler[type]) } delete dom.eventQueue[type]; }; /*判断事件队列长度 * *@param {dom-DOM}和{type-string} *@caller bind() unBind() */ function queueLength(dom,type){ var index = 0; for (var length in dom.eventQueue[type]){ index++ ; } return index; };
使用方法
var oBtn = document.getElementById('btn'); //绑定事件 //为button同时绑定三个click事件函数 //ie6-ie8下执行顺序不变 bind(oBtn,'click',function(){ alert(1); }) bind(oBtn,'click',function(){ alert(2); },'myFn') bind(oBtn,'click',function(){ alert(3); }) //移除事件 //移除所有绑定的click事件函数,支持移除匿名函数 unBind(oBtn,'click') //只移除标识符为myfn的事件函数 unBind(oBtn,'click','myFn')
程序思路
程序主要思路就像将事件队列作为dom元素对象的一个属性,添加在dom元素上,而不会污染全局环境,这样可以解决不同dom元素绑定不同事件类型的多个事件函数的数据存储问题。
//dom元素上的事件队列 dom{ eventQueue : { 'click' : { fnQueue1 : function, myfn : function, fnQueue3 : function } 'mouseover' : { fnQueue1 : function, fnQueue2 : function } } }
每次先把事件函数添加到对应事件类型的事件队列中,只绑定一次事件。触发事件时执行handler事件函数,handler则遍历执行事件队列中的事件函数。
unBind()如果没有传入标识符,则移除所有绑定的事件函数,支持移除匿名函数,如果有标识符则移除指定的事件函数。
程序逻辑并不复杂,可能有bug和性能问题,有兴趣可以指导交流下。