IE的attachEvent,危险!

今天我们来讨论一个现代的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中运行,这是一件多么值得高兴的事情!

你可能感兴趣的:(跨浏览器,事件监听,跨浏览器事件绑定,事件删除,抛弃IE的事件绑定)