今天我们来讨论一个现代的IE的事件监听中的缺点以及我们如何解决它!
事件绑定分为传统的事件绑定和现代的事件绑定
传统的事件绑定的一般形式为:
window.onload = fn;
现代的事件绑定分为W3C的addEventLienter、removeEventListener和
IE的attachEvent、detachEvent。
// 跨浏览器事件绑定
function addEvent(obj, type, fn) {
if (typeof obj.addEventListener !== 'undefined') {
obj.addEventListener(type, fn, false);
} else if (typeof obj.attachEvent !== 'undefined') {
obj.attachEvent('on' + type, fn);
}
}
// 跨浏览器事件解除
function removeEvent(obj, type, fn) {
if (typeof obj.removeEventListener !== 'undefined') {
obj.removeEventListener(type, fn, false);
} else if (typeof obj.detachEvent !== 'undefined') {
obj.detachEvent('on' + type, fn);
}
}
但是,我们知道现代的IE事件绑定问题多多,且有内存泄露的问题。
接下来我们来讨论一下其存在的问题和解决方法:
1.支持同一元素的同一事件句柄可以绑定多个监听函数
传统的事件绑定—-NO
window.onload = function () {
alert(1);
}
window.onload = function () {
alert(2);
}
window.onload = function () {
alert(3);
}
// 只执行第三个,前两个被覆盖
现代的事件绑定—-OK,但是执行顺序没有按照绑定的顺序执行
addEvent(window, "load", function () {
alert(1);
});
addEvent(window, "load", function () {
alert(2);
});
addEvent(window, "load", function () {
alert(3);
});
// 在FF中没有问题,返回1、2、3
// 在IE6中也没有问题,只是返回的顺序为3、2、1
2.如果在同一元素的同一事件句柄上多次注册同一个元素,那么第一次注册后的所有注册将被忽略
先在html中定义一个按钮
<input type="button" id="btn" value='按钮'/>
用传统方式测试—-OK
var btn = document.getElementById("btn");
btn.onclick = fn;
btn.onclick = fn;
function fn() {
alert(1);
}
// 只执行了一次
用现代绑定测试—-W3C is OK, yet IE 6 is NO
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn); addEvent(btn, 'click', fn); function fn() {
alert(1);
}
// W3C也只执行了一次
// IE 6执行了两次
3.函数体内的this应该指向的是正在处理事件的节点—W3C is OK, yet IE 6 is NO
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn);
function fn() {
alert(this.value);
}
// W3C返回的是按钮
// IE6返回的是undefined
解决方法,用call方法进行对象冒充
function addEvent(obj, type, fn) {
if (typeof obj.addEventListener !== 'undefined') {
obj.addEventListener(type, fn, false);
} else if (typeof obj.attachEvent !== 'undefined') {
obj.attachEvent('on' + type, function () {
// 没错,从这里开始冒充
fn.call(fn);
});
}
}
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn);
function fn() {
alert(this.value);
}
// 都可以返回指定对象的值
但是对象冒充又带来了两个新的问题:无法标准化event和删除事件
先看删除事件
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn);
removeEvent(btn, 'click', fn);
function fn() {
alert(this.value);
}
// W3C可以删除
// IE6不行
这是为什么呢?
因为在添加事件绑定时,执行的函数为一个匿名函数,而删除的时候为fn,两个函数不一样
另一个问题:event标准化
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn);
function fn(e) {
alert(e.clientX);
}
// W3C可以返回
// IE 6不行
我们可以向call传递event来解决
function addEvent(obj, type, fn) {
if (typeof obj.addEventListener !== 'undefined') {
obj.addEventListener(type, fn, false);
} else if (typeof obj.attachEvent !== 'undefined') {
obj.attachEvent('on' + type, function () {
fn.call(obj, window.event);
});
}
}
// 都OK
可以看出,对象冒充虽然解决了IE的this问题,但是带来了不能删除事件绑定的新问题
4.监听函数的执行顺序应当按照绑定的顺序执行。
没有解决,在1中已经展示,这里不再展示
5.在函数体内不用使用 event = event || window.event;来标准化Event对象,可以解决
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn);
function fn(e) {
alert(e.clientX);
}
// 在现代事件绑定都支持
从上述例子可以看出,现代的事件绑定IE版本的还有3个问题没有解决:
第一:无法删除事件
第二:无法顺序执行
第三:IE绑定存在内存泄露问题,也就是this的问题
因此我们需要抛弃IE的attachEvent和detachEvent,模仿传统事件绑定IE
由于代码计较长,我就写上关键的注释,不再啰嗦了。
// 跨浏览器事件绑定
function addEvent(obj, type, fn) {
if (typeof obj.addEventListener !== 'undefined') {
obj.addEventListener(type, fn, false);
} else {
// 创建一个可以保存事件的哈希表
if (!obj.events) {
obj.events = {};
}
// 第一次执行
if (!obj.events[type]) {
// 创建一个可以保存事件处理函数的数组
obj.events[type] = [];
// 存蓄第一个事件处理函数
// 如果绑定的事件以及存在,我们就把事件处理函数放入数组中
if (!obj.events['on' + type]) {
obj.events[type].push(fn);
}
} else {
// 从第二个事件处理函数开始存储
// 同一函数进行屏蔽,如果没有同一元素的同一事件的同一个事件处理程序注册,那么就添加进来
if (!addEvent.equal(obj.events[type], fn)) {
obj.events[type][addEvent.ID++] = fn;
}
}
// 执行所有的事件处理函数
obj['on' + type] = addEvent.exec;
}
}
// 将IE的event对象配对
addEvent.fixEvent = function (event) {
addEvent.fixEvent.preventDefault = addEvent.fixEvent.preventDefault;
addEvent.fixEvent.stopPropagation = addEvent.fixEvent.stopPropagation;
return event;
}
// IE阻止默认行为
addEvent.fixEvent.preventDefault = function () {
this.returnValue = false;
}
// IE取消冒泡
addEvent.fixEvent.stopPropagation = function () {
this.cancelBubble = true;
}
// 同一注册函数进行屏蔽
addEvent.equal = function (es, fn) {
for (var i in es) {
if (es[i] === fn) {
return true;
}
}
return false;
}
// 执行事件处理函数
addEvent.exec = function (e) {
var e = addEvent.fixEvent(window.event);
var es = this.events[e.type];
for (var i in es) {
es[i].call(this, e);
}
}
// 为每个事件添加一个计数器
addEvent.ID = 1;
// 跨浏览器事件解除
function removeEvent(obj, type, fn) {
if (typeof obj.removeEventListener !== 'undefined') {
obj.removeEventListener(type, fn, false);
} else {
for(var i in obj.events[type]) {
if (obj.events[type][i] === fn) {
delete obj.events[type][i];
}
}
}
}
接下来,我们来测试一下是否能够解决上面的5个问题:
问题一:同一元素的同一事件绑定多个函数——OK
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn1);
addEvent(btn, 'click', fn2);
function fn1() {
alert(1);
}
function fn2() {
alert(2);
}
// 结果:1,2
问题二:同一元素的同一事件绑定重复函数,是否会有屏蔽——OK
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn1);
addEvent(btn, 'click', fn1);
addEvent(btn, 'click', fn1);
function fn1() {
alert(1);
}
function fn2() {
alert(2);
}
// 只返回一次,剩下两个重复的也被屏蔽
问题三:函数体内的this应该指向的是否是正在处理事件的节点—–OK
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn1);
function fn1() {
alert(this.value);
}
// 返回按钮的值,并非undefined
问题四:监听函数的执行顺序应当按照绑定的顺序执行—-OK
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn3);
addEvent(btn, 'click', fn1);
addEvent(btn, 'click', fn2);
function fn1() {
alert(1);
}
function fn2() {
alert(2);
}
function fn3() {
alert(3);
}
// 返回3、1、2
问题五:在函数体内不用使用 event = event || window.event来标准化Event对象—-OK
html部分
<a id="blog" href="http://blog.csdn.net/super_yang_android">光明大神棍的博客</a>
var blog = document.getElementById("blog");
addEvent(blog, 'click', function (e){
e.preventDefault(); // 点击超链接无效
})
遗留问题一:对象冒充不可以移除事件—-现在OK
var btn = document.getElementById("btn");
addEvent(btn, 'click', fn3);
addEvent(btn, 'click', fn1);
addEvent(btn, 'click', fn2);
removeEvent(btn, 'click', fn2);
function fn1() {
alert(1);
}
function fn2() {
alert(2);
}
function fn3() {
alert(3);
}
// 返回3,1
由上我们不难看出,我们自定义的事件绑定和删除可以轻松的在IE中运行,这是一件多么值得高兴的事情!