随着Ajax和RIA越来越成为主流,Javascript对事件(Event)的支持也得到了越来越多的关注。像雅虎这样的公司正在突破RIA的极限,让web应用程序在浏览器中更有效的运行,就像桌面应用程序一样。雅虎的邮箱应用就是一个很好的例子。
雅虎的一些工程师给我们展示了提高Javascript应用程序性能的技术。其中有提到强大的事件处理架构。提高性能的要旨就是用事件委托(Event Delegation)而非传统的事件处理(Event Handling)。
我发现一个问题是,网上大部分的例子是用YUI编写的,隐藏了背后的Javascript。在这边文章中,我会给出纯Javascript版本的事件代理的例子。
传统的Javascript事件处理和Unobtrusive Javascript
传统的Javascript时间处理并不是有效率的。一个强大的Ajax和RIA应用程序,它会有大量的用户交互接口。所有的这些用户交互接口会有一个对应的事件处理,所有的这些事件处理需要捆绑在一起才可以被用户触发。
如果用现今的技术,Unobtrusive scripting,这是一个将事件和脚本从(X)HTML中剥离的技术。它将访问DOM并通过content,PResentation, behivor分离的技术来附加脚本到对象中。(X)HTML中再也不会出现onclick这样的时间处理代码。
在传统的Javascript中,把所有的事件捆绑在一起是有代价的。不仅是捆绑它们的步骤很多,而且重复的代码占用浏览器内存。不仅仅如此,如果你改变DOM,新添加的元素不会注意到onload事件,你需要重新设置事件。
请看下面的代码:
<ul id="listing"> <li><a href="#">Handlers Test</a></li> <li><a href="#">Handlers Test</a></li> <li><a href="#">Handlers Test</a></li> <li><a href="#">Handlers Test</a></li></ul>
window.onload = function(){ var x = document.getElementById("listing"); x = x.getElementsByTagName("a"); for (var i = 0; i < x.length; i++){ (function(){ var z = i; x[i].onclick = function(){ alert("clicking" + z); return false; }; })(); }}
这段代码会查找里列表中所有的锚点,并给它们分别添加一个匿名函数。这很好的做到了unobtrusive。但是我们却在点击事件上消耗了大量的浏览器内存。你能想象如果一个更大的列表是什么样子吗?
合并所有的用户交互接口,你就会有类似下面的一个场景:
对象+事件,对象+事件,对象+事件... = 一个捆绑在一起的集合
事件委托(Event Delegation)和事件冒泡(Event Bubbling)
基本原理是将事件绑定在文档对象中更小的一个集合上,而不是绑定到每个元素上。因为Javascript的事件冒泡机制(Event Bubbling)暴露this,所以可以检测到this指向的当前元素。事件冒泡的主张是,每一个被点击的元素,会注册一个点击事件,该事件会沿着DOM的树向上冒泡直到DOM的根结点。你可以捕捉这个事件,并检测页面中最初的事件源和当前元素。
雅虎的很多工程师用YUI展示了事件委托的例子,这里我将用Javascript重新演示这个例子。本例子给出了两个可以折叠的无序列表。第一个列表用传统的事件处理来实现,第二个用事件委托来实现。这两个例子都可以正常的工作,一旦你修改DOM,则只有第二个例子能工作了。
首先你需要一个函数来获取事件对象(Event Target)(W3C事件模型中的定义),或者事件源(Event Source)(IE浏览器中的定义),下面就是这个神奇的函数:
// get and identify the source of the event objectfunction getTarget(x){ x = x || window.event; return x.target || x.srcElement;}
你可以通过下面的代码来获取事件的目标节点
two.onclick = function(e){ // delegate, pass in the event object ! ! var t = getTarget(e); // take conditional action ! ! if (t.nodeName.toLowerCase() === 'a') {.....
接下来你就可以在onclick事件上写需要的代码了。完整的例子请看这里,Javascript代码如下:
// event delegationvar setup = function(){ // just get our stuff var one = document.getElementById("collapse_one"); var two = document.getElementById("collapse_two"); one.className = "dynamic"; two.className = "dynamic"; // start traditional event handlers function toggle(el) { var ul = el.parentNode.getElementsByTagName('ul')[0]; if (ul.style.display == 'none' || ul.style.display == ''){ ul.style.display = 'block'; } else { ul.style.display = 'none'; } return false; }; var uls = one.getElementsByTagName("ul"); for (var i=0; i<uls.length; i++){ var parentLink = uls[i].parentNode.getElementsByTagName('a')[0]; parentLink.onclick = function(){ return toggle(this); }; }; // end traditional event handlers // start event delegation var uls = two.getElementsByTagName("ul"); two.onclick = function(e){ var t = getTarget(e); // delegate!! if (t.nodeName.toLowerCase() === 'a' && t.parentNode.getElementsByTagName('ul').length > 0) { var ul = t.parentNode.getElementsByTagName('ul')[0]; if (ul.style.display == 'none' || ul.style.display == ''){ ul.style.display = 'block'; } else { ul.style.display = 'none'; } return false; }; }; function getTarget(x){ // here's the magic, really simple stuff x = x || window.event; return x.target || x.srcElement; } // end event delegation // general utilities function addElements(){ var extraHTML = ['<li><a href="#">New Item</a><ul>' , '<li><a href="#">Sub Item 1</a><li><a href="#">Sub Item 2</a>' , '<li><a href="#">Sub Item 3</a><li><a href="#">Sub Item 4</a>' , '<li><a href="#">Sub Item 5</a><li><a href="#">Sub Item 6</a>' , '</li></ul></li>'].join(''); one.innerHTML += extraHTML; two.innerHTML += extraHTML; } // using, gasp! event handler document.getElementById("myButton").onclick = addElements;};window.onload = setup;View Code
本例中,我们将事件绑定在指定元素的上层节点,其实也可以直接将事件绑定在docuemnt节点上。
关于事件委托的结论
原文地址:http://v1.cherny.com/webdev/70/javascript-event-delegation-and-event-hanlders
新闻热点
疑难解答