设计错误导致的。当我们编写脚本的时候创建了交叉引用,例如如下代码: window.onload = function () {
var x = document.getElementsByTagName(’H3’);
for (var i=0;i<x.length;i++)
{
x[i].onclick = openClose;
x[i].relatedElement = x[i].nextSibling; // simplified situation
x[i].relatedElement.relatedElement = x[i];
}
}或者在函数中使用脚本语言最常见的闭句Closures的时候,IE都无法回收内存。而闭句在给DOM对象注册事件处理器(event handler)的时候最为常用。Novemberborn提供了一些example可以让你运行并切实感受到这个bug。
我最喜爱的QuirkMode 去年初意识到这个bug存在巨大隐患,觉得有必要呼吁广大web开发者关注并竭力避免这个问题,于是举办了一个慈善邀请赛,鼓励大家提交各自addEvent/removeEvent 方案。并终于在去年10月下旬宣布了他们认为的胜利者:John Resig,让John赢得胜利的代码如下:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj[’e’+type+fn] = fn;
obj[type+fn] = function(){obj[’e’+type+fn]( window.event );}
obj.attachEvent( ’on’+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( ’on’+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}
QuirkMode 对选择John为胜利者的解释概括来说就是以上代码最简洁有效,在避免内存问题的同时还巧妙的保证了this关键字在ie的attachEvent中能正常工作。缺点当然还是存在:
不支持 Netscape 4 和 Explorer 5 Mac。(有可能国内的程序员会嗤之以鼻,但国外很强调广泛的兼容性)
在 removeEvent 中遗漏了remove obj["e"+type+fn]。
总之不管怎么说,简单取胜。
结果一出,众多参赛与评论者不服气,很快又挑出了John的代码的几处毛病:
addEvent中本身就使用了闭句,所以没有根本解决IE内存泄露的问题。
没有解决同类型的事件可能被重复注册而被IE重复执行的问题。
几个高手于是提出了改进性的方案: /*
Original idea by John Resig
Tweaked by Scott Andrew LePera, Dean Edwards and Peter-Paul Koch
Fixed for IE by Tino Zijdel (crisp)
Note that in IE this will cause memory leaks and still doesn’t quite function the same as in browsers that do support the W3C event model:
- event execution order is not the same (LIFO in IE against FIFO)
- functions attached to the same event on the same element multiple times will also get executed multiple times in IE
*/
function addEvent( obj, type, fn ) {
if (obj.addEventListener)
obj.addEventListener( type, fn, false );
else if (obj.attachEvent) {
obj["e"+type+fn] = fn;
obj.attachEvent( "on"+type, function() { obj["e"+type+fn](); } );
}
}
function removeEvent( obj, type, fn ) {
if (obj.removeEventListener)
obj.removeEventListener( type, fn, false );
else if (obj.detachEvent) {
obj.detachEvent( "on"+type, obj["e"+type+fn] );
obj["e"+type+fn] = null;
}
}
很明显,虽然修正了John代码的一些不足。但内存泄露依然存在,部分浏览器依然不支持,还是无法避免ie重复注册。另外根据注释:当在同一个对象上注册多个事件处理器的时候,IE与其他浏览器的执行顺序是不同的,这又是一个隐患。
几天之后,一个被认为最严谨的方案由Dean Edwards 提出。Dean他的方案与众不同:
不执行对象检测(Object detection)
没有调用 addeventListener/attachEvent 方法
保持this关键字的运行于正确的上下文环境
正确传递 event 对象参数
完全跨浏览器至此(包括IE4和NS4)
不存在内存泄露
Dean的代码如下: // written by Dean Edwards, 2005
// http://dean.edwards.name/function ;addEvent(element, type, handler) {
// assign each event handler a unique ID
// 为事件处理函数设定一个唯一值
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
// create a hash table of event types for the element
if (!element.events) element.events = {};
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
// 如果对象已经注册有事件处理,那么要保留下来,并保存为第一个
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
// 指派一个全局函数做统一的事件处理,同时避免了反复注册
element["on" + type] = handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1;function removeEvent(element, type, handler) {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
};function handleEvent(event) {
// grab the event object (IE uses a global event object)
event = event || window.event;
// get a reference to the hash table of event handlers
// 这里的 this 随 handlerEvent function 被触发的source element 变化而变化
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
//这样写才能保证注册的事件处理函数中的 this 得到正确的引用,直接handlers[i]()是不行的
this.$$handleEvent = handlers[i];
this.$$handleEvent(event);
}
};
这段代码相比之前就大了不少了,不过确实很精妙。可是这段代码却引入了其他的问题,比如无法处理事件处理函数的返回值,for..in循环可能因为(Object.prototype)的错误应用而中断等等...很快Dean推出一个"updated version"。
要做到最好真的好辛苦。
目前似乎Dean的最终版本是最全面的解决方案。不过就我个人意见,感觉有些吹毛求疵了。尽量使用浏览器本身的实现和保持简单是我一贯坚持的主张。但洋人这种严谨的态度,还是让我深深敬佩。
分享到:
相关推荐
本文将深入探讨如何实现最佳的`addEvent`事件绑定方法,以及如何解决与之相关的内存泄漏和兼容性问题。 首先,让我们回顾一下传统事件绑定的方式,例如在给定的代码示例中,通过循环将`openClose`函数直接赋值给`H3...
事件上报addEvent.vue
该集合包含了核心的几个函数:addEvent()、removeEvent()、handleEvent()和fixEvent(),用以添加、移除、处理事件以及修正不同浏览器间的事件对象差异。 addEvent()函数是一个跨浏览器的事件监听器,其作用是在元素...
### 驱动事件的addEvent.js代码解析 #### 一、概述 在现代Web开发中,事件处理是实现交互式用户体验的关键技术之一。不同浏览器对DOM(文档对象模型)事件的支持有所不同,为了确保跨浏览器的兼容性,开发者通常会...
可见前辈的6年前的努力:最佳的addEvent是怎样诞生的,后起之秀jQuery也付出了一千六百多行血汗代码(v 1.5.1)搞定了6年后出现的各种核的浏览器。 我参考前辈的代码以及自己的理解尝试写了一个事件框架,我的框架...
事件监听器 如果您需要支持旧版IE,则addEventListener()与addEvent()简单函数。安装npm install event-listener用法var listen = require ( 'event-listener' )// Returns an object with a .remove() methodvar ...
2. **事件绑定**:MooTools 使用 `addEvent` 方法来绑定事件,例如,我们可以将点击事件绑定到抽奖按钮上,当用户点击时触发抽奖逻辑。 3. **随机数生成**:抽奖的关键在于生成随机数,MooTools 通过 `Math.random...
在这段代码中,我们定义了一个名为 `addEvent` 的通用函数,它接受三个参数:`obj` 表示要绑定事件的 DOM 元素,`type` 表示事件类型(如 click、mouseover 等),`fn` 表示事件处理函数。该函数首先检查目标对象...
本文将详细介绍十个最常用的自定义函数之一:`addEvent` 函数及其变体。 #### 二、`addEvent` 函数概述 `addEvent` 函数主要用于向 DOM 元素添加事件监听器。由于不同的浏览器对于事件绑定的支持存在差异,因此需要...
然后,讨论了一个通用的、跨浏览器的绑定方法,包括 John Resig 和 Dean Edward 所写的 addEvent() 函数。 绑定大事监听函数的通用方法是 JavaScript 编程中非常重要的一部分。一个好的 addEvent() 方法应当达到...
jQuery的事件系统,特别是1.9.1版本,它在很大程度上受到了DeanEdwards的跨浏览器AddEvent()设计的影响。DeanEdwards的AddEvent()设计是一个早期尝试解决浏览器之间事件处理不兼容问题的方法。为了深入理解jQuery...
$('tween_button').addEvent('click', function() { $('tweener').tween('width', '300px'); }); }); ``` 在这个例子中,当用户点击按钮时,div的宽度会从当前值平滑地变为300像素。 除了`.tween()`,Mootools...
**MoolTools框架详解** MoolTools框架是一个备受赞誉的轻量级技术框架,它以其高效、实用的特点在IT行业中赢得了良好的口碑。这个框架的设计理念是简化开发过程,提高开发效率,同时也注重代码的可读性和可维护性,...
对于事件的操作无非是addEvent,fireEvent,removeEvent这三个事 件方法。一般lib都会对浏览器的提供的函数做一些扩展,解决兼容性内存泄漏等问题。第三个问题就是如何得到domReady的状态。 6.1 event的包裹 浏览器的...
通过阅读这份文档,开发者不仅可以了解Mootools的基本用法,还能深入理解其设计理念和最佳实践。建议读者结合实际项目练习,以巩固理论知识并提升实战能力。 总的来说,Mootools 1.2是一个功能强大且灵活的...
Mootools中的事件绑定非常直观且高效,其`addEvent`方法与jQuery中的`bind`或`on`类似,用于将事件监听器附加到DOM元素上。例如,以下代码演示了如何使用`addEvent`方法为`<a>`元素绑定点击事件: ```javascript $...