最佳的addEvent事件绑定是怎样诞生的

当我们编写脚本的时候创建了交叉引用,例如如下代码:

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的最终版本是最全面的解决方案。不过就我个人意见,感觉有些吹毛求疵了。尽量使用浏览器本身的实现和保持简单是我一贯坚持的主张。但洋人这种严谨的态度,还是让我深深敬佩。

(0)

相关推荐

  • Javascript 事件流和事件绑定

    事件流 浏览器中的事件流意味着页面上可有不仅一个,甚至多个元素响应同一个事件.而这一个或多个元素响应事件发生的先后顺序在各个浏览器(主要针对IE和Netscape)上是不同的. 冒泡型事件(Dubbed Bubbling) IE上的解决方案就是冒泡型事件(Dubbed Bubbling).冒泡型事件的基本思想是,事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发. 示例(1):点击我触发冒泡型事件流 示例(1)的XHTML代码结构:<span id="cnt0

  • javascript 事件绑定问题

    子页面HTML: 复制代码 代码如下: <input type="checkbox" name="check" onclick="CheckedBox(this)" value="a" /> 子页面JS: 复制代码 代码如下: function CreateIOC(text,value) { var o = document.createElement("a"); o.id = value; o

  • Javascript函数加壳多用于事件绑定

    function addShell(func,name,obj) { func=Function(func); if(!obj) obj=window; var old = obj[name]; if (typeof obj[name] != 'function') { obj[name] = func; } else { obj[name] = function() { var ja=[]; var t = obj[name].arguments; for(var x=0;x [Ctrl+A

  • img onload事件绑定各浏览器均可执行

    在需要对img进行onload事件绑定的时候,一般大家都会想到用常规的方法进行事件绑定,如下: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtm

  • jQuery事件绑定.on()简要概述及应用

    前几天在看<jquery基础教程>,看到事件委托的时候,关于live()方法讲的不是很详细,就去搜了一下关于live()和delegate()的. 然后在一处看到live()已经被移除了,囧,然后去看了最新的jq源码,果然被移除了,现在是1.9.1版本,不知道live()是在之前哪个版本被移除的,惭愧啊,之前都没留意. 看源码发现bind()和delegate()都是由on()实现的.on()的描述如下: 复制代码 代码如下: .on( events [, selector ] [, data

  • javascript 处理事件绑定的一些兼容写法

    绑定事件 复制代码 代码如下: var addEvent = function( 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"

  • 关于jquery input textare 事件绑定及用法学习

    (1)jquery 绑定事件 目前1.7以上,jquery的事件绑定已经用on替换了原来的bind: 区别:(个人理解)bind是一次绑定事件到每一个子节点:on是只绑定到父节点,然后冒泡到各个子节点: 用法:bind 一个事件,一个方法:$(".class input").bind('click',function(e){...;e.stopPropagation;}) 两个事件,一个方法:$(".class input").bind('click mouseov

  • 最佳的addEvent事件绑定是怎样诞生的

    当我们编写脚本的时候创建了交叉引用,例如如下代码: 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.rel

  • 关于JavaScript中事件绑定的方法总结

    最近收集了一些关于JavaScript绑定事件的方法,汇总了一下,不全面,但是,希望便于以后自己查看. JavaScript中绑定事件的方法主要有三种: 1 在DOM元素中直接绑定 2 JavaScript代码中直接绑定 3 绑定事件监听函数 一.在DOM元素中直接绑定 也就是直接在html标签中通过 onXXX="" 来绑定.举个例子: <input type="button" value="点我呦" onclick="aler

  • 浅谈JavaScript事件绑定的常用方法及其优缺点分析

    传统方式  element.onclick = function(e){ // ... };  1.  传统绑定的优点 非常简单和稳定,可以确保它在你使用的不同浏览器中运作一致 处理事件时,this关键字引用的是当前元素,这很有帮组 2. 传统绑定的缺点 传统方法只会在事件冒泡中运行,而非捕获和冒泡 一个元素一次只能绑定一个事件处理函数.新绑定的事件处理函数会覆盖旧的事件处理函数 事件对象参数(e)仅非IE浏览器可用 W3C方式  element.addEventListener('click'

  • JS的事件绑定深入认识

    一.传统事件模型 传统事件模型中存在局限性. 内联模型以HTML标签属性的形式使用,与HTML混写,这种方式无疑造成了修改以及扩展的问题,已经很少使用了. 脚本模型是将事件处理函数写到js文件中,从页面获取元素进行对应事件函数的绑定以触发执行.但也存在不足之处: 1.一个事件绑定多个事件监听函数,后者将覆盖前者. 2.需要限制重复绑定的情况 3.标准化event对象 二.现代事件绑定 DOM2级事件定义了两个方法用于添加.删除事件:addEventListener().removeEventLi

  • javascript事件绑定学习要点

    事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型).现代事件绑定在传统绑定上提供了更强大更方便的功能. 一 传统事件绑定的问题 传统事件绑定中的内联模型不做讨论,基本很少去用.先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数.传统绑定如: window.onload=function(){ var box=document.getElementById('box'); box.onclick = function(){ alert('Le

  • JavaScript 事件绑定及深入

    事件绑定分为两种: 一种是传统事件绑定(内联模型/脚本模型);上一章内容; 一种是现代事件绑定(DOM2级模型);现代事件绑定在传统事件绑定基础上提供了更强大的功能; 一 传统事件绑定的问题 // 脚本模型将一个函数赋值给一个事件处理函数; var box = document.getElementById('box'); // 获取元素; box.onclick = function(){ // 元素点击触发事件; alert('Lee'); } // 问题一:一个事件处理函数触发两次事件;

  • ES6中javascript实现函数绑定及类的事件绑定功能详解

    本文实例讲述了ES6中javascript实现函数绑定及类的事件绑定功能的方法.分享给大家供大家参考,具体如下: 函数绑定 箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call.apply.bind).但是,箭头函数并不适用于所有场合,所以 ES7 提出了 " 函数绑定 " ( function bind )运算符,用来取代call.apply.bind调用.虽然该语法还是 ES7 的一个提案,但是 Babel 转码器已经支持. 函数绑定运算符是并排的两个双冒号

  • jQuery实现的事件绑定功能基本示例

    本文实例讲述了jQuery实现的事件绑定功能.分享给大家供大家参考,具体如下: HTML正文: 用户名:<input type="text" value="邮箱/用户名/手机号" id="login"/><br> 密 码:<input type="password" id="passwd"><br> <input type="button&qu

  • AngularJS中directive指令使用之事件绑定与指令交互用法示例

    本文实例讲述了AngularJS中directive指令使用之事件绑定与指令交互用法.分享给大家供大家参考,具体如下: AngularJS中模板的使用,事件绑定以及指令与指令之间的交互 <!doctype html> <html ng-app="myapp"> <head> <meta charset="utf-8"/> </head> <body ng-controller="Shield

随机推荐