Event delegation in JavaScript
作者:Nicholas C. Zakas
著作:《Javascript高级程序设计》,《ajax实战》
原文链接:http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/
传统的事件句柄
事件委托的实现十分简单,使用一个单独的事件句柄去管理整个页面特殊类别的事件。这不是一个新的想法,(原文:but it is an important one to grasp for performance) 。许多时候,你看得的都像下面的web应用的代码
document.getElementById("help-btn").onclick = function(event){
openHelp();
};
document.getElementById("save-btn").onclick = function(event){
saveDocument();
};
document.getElementById("undo-btn").onclick = function(event){
undoChanges();
};
传统的编码方式需要为每个元素事件句柄如果网站程序交互比较少这是OK的,但是大型web应用,尽管定义大量的事件句柄并没有太大的影响,麻烦的并不一定是速度的问题也是内存占用的问题。如果成百上千的程序交互最终将处理大量DOM元素之间的关系和编写大量的javascript代码,这就要求更多的内存来支持你的web应用,通常程序也会运行的很慢,事件委托帮助你解决这个难题。
事件的冒泡与捕获
如果不是动态绑定事件,事件委托是可能的。早期的web开发,浏览器供应商都会面临一个严肃的问题,就是当你点击网页中一个区域 什么元素是你实际想要触发的。这个问题伴随着页面元素交互的定义问题,点击任意一个元素的内部边界是模糊的。
点击一个按钮看看,你实际点击在一个按钮元素的内部,也是body元素内部,还是html元素的内部。
当这个问题出现的时候,有两个主要的浏览器:Netscape和IE。他们各自决定以不同的方式解决这个问题。网景公司定义的方法称为事件捕捉,一旦事件触发最先影响到DOM树最顶层对象(document),然后一直影响到事件源。因此,在这个例子中,单击事件的事件捕获最早由document处理,然后在<HTML>元素,然后是<body>,最后是<button>元素。
在IE中处理方式正好相反。 IE团队定义了一个方法称为事件冒泡。事件冒泡说的是,事件源元素的事件应该第一个被触发,那么它的父元素应该接收事件,然后它的父元素的父元素,等等,直到最后document对象接收事件。虽然document没有一个独特的视觉表现从<html>分开,但仍然被认为是<html>的父元素,从而继续冒泡DOM结构顶端。前面的例子可以看<button>首先接收到事件,然后就到<body>,再后到<html>,最后到达document对象。
当定义DOM标准时,W3C组织显然发现了事件的捕获机制与冒泡机制的优点,因而在DOM 2事件制定中支持这两种机制。首先,document对象接收一个事件,然后在捕获期间找到最具体的事件影响的元素,一旦这个元素绑定这个事件,事件将以冒泡的形式返回到document对象。DOM addEventListener() 函数接受三个参数,分别是事件名、事件句柄、支持事件冒泡还是捕获机制布尔值,绝大部分的web开发者会把这个最后那个参数设置为false,以期在IE中能有attachEvent()函数一样功能。
//bubbling phase handler
document.addEventListener("click", handleClick, false);
//capturing phase handler
document.addEventListener("click", handleClick, true);
通过一个附加属性来进行事件与事件句柄绑定(element.onclick = function(){}),事件将被自动作为冒泡机制来处理(这是为了向后兼容),几乎每一款浏览器除了IE(即便是IE8)都支持DOM 2事件处理捕获与冒泡机制,而IE仍然只支持冒泡。
在冒泡机制中使用事件委托
事件委托在关键在于使用冒泡处理最顶层元素(通常是document对象),并非所有的事件都会冒泡,但是但鼠标和键盘事件会,也是也我们感兴趣的事件。回顾早先的事例,你可以为document对象绑定一个click事件,然后判断事件源,根据事件源不同来执行不同函数。
document.onclick = function(event){
//IE doesn't pass in the event object
event = event || window.event;
//IE uses srcElement as the target
var target = event.target || event.srcElement;
switch(target.id){
case "help-btn":
openHelp();
break;
case "save-btn":
saveDocument();
break;
case "undo-btn":
undoChanges();
break;
//others?
}
};
使用事件委托,可以把众多的事件处理缩减到一个,当前所有click事件都被一个根据事件源不同进行不同操作的函数处理,你还可以以同样的方式创建mousedown, mouseup, mousemove, mouseover, mouseout, dblclick, keyup, keydown, keypress事件处理程序。还有一句忠告,mouseover、mouseout因为它们的特性决定了他们通过事件委托很难处理。
注:你也可以使用事件捕获机制来建立事件委托,但是它将不能在IE浏览器中运行。
好处:
1,更少的函数管理
2,使用更少内存
3,更少的DOM处理Javascript代码
4,改变DOM元素的innerHTML时不必担心事件绑定被删除
从传统的事件处理转移到事件委托改善了世界各地大型web应用的性能。这项目工作十分重要,YUI与JQuery 已经把事件委托提炼为核心接口。只要花很小的代价实现事件委托,但性能有十分明显的提升。当你把几十个事件处理转化为一个事件处理程序好处显尔益见。进行事件委托的尝试吧,或许你将再也不会使用传统的事件处理方式了。