IE的attachEvent

事件绑定分为传统的事件绑定和现代的事件绑定
传统的事件绑定的一般形式为:

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,271评论 19 139
  • 什么是事件: 我们可以简单的把事件理解为浏览器的感知系统。比如说:他可以感觉到用户是否点击(click)了页面、鼠...
    张松1366阅读 11,806评论 1 6
  • 以下文章为转载,对理解JavaScript中的事件处理机制很有帮助,浅显易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy阅读 8,227评论 1 10
  • 我们绝对要过一个无悔的人生!总有一天,我们要踏上这片海洋,按照自己的梦想去闯荡!我们要过上最自由的人生!---路飞...
    猫黍阅读 4,971评论 0 3
  • 感恩爸爸,他今天带我们去摘葡萄。那儿的葡萄可甜啦,我们边摘边吃。可好玩儿啦! 感恩葡萄园的老板,他大方的给我们切甜...
    寸心洁白阅读 1,477评论 0 0

友情链接更多精彩内容