原文地址:http://www.learningjquery.com/2008/03/working-with-events-part-1
原文作者:  Karl Swedberg

CSS和JavaScript有很多的不同,其中大多数区别都很明显不用说出来。然而,他俩的一处区别值得一 提,因为这个区别常常引起困惑和吃惊,尤其是对那些从CSS专家转职为jQuery新手的人。事实上,这是早在2006年我向jQuery邮件列表提出的 第一个问题。从那之后,我至少每周能看到类似的问题,有时候甚至每天都有,尽管jQuery已经提供了FAQ page and  三个 插件在解决这个问题。

CSS和JavaScript如何不同?

最重要的区别是什么?
在CSS中,样式规则总是自动应用于所有匹配样式选择器的Element上的,不管这些Element是什么时候加入到DOM中的。
在JavaScript中,为Element注册的事件处理器只会被应用于事件附加的那个 时候DOM中存在的那些Element之上。如果我们在后向DOM中添加同一类型的Element(不管是DOM操作还是AJAX方式),CSS总是能够 对这些Element显示相同的样式,但是JavaScript的事件处理器就不会自动应用到新加入的Element。
For example, let’s say we have “<button>Alert!</button>” in our document, and we want to attach a click handler to it that generates an alert message. In jQuery, we might do so with the following code:

举个例子,假如我们html文档中有”<button>Alert!</button>“,我们想要为它加上点击事件处理,让它在被点击的时候产生一个alert消息。在jQuery中用如下的代码实现:

$(document).ready(function() {
  $('button.alert').click(function() {
 alert('this is an alert message');
  });
});

这里我们在页面加载完成之后为定义了class等于”alert”的button注册一个点击事件。按钮已经在这了,我们为其绑定了一个点击事件。如果我们之后再动态插入第二个按钮<button>,然而这个时候第二个按钮是完全不知道点击事件处理器的。点击事件在第二个按钮存在之前已经处理完了。因此第二个按钮不会产生alert消息。

我们可以测试一下刚才讨论的事件处理器行为。我已经在下面的按钮上加上了上面的三行jQuery代码,当你点击的时候会弹出一个alert消息。点击试试:

对新加的Elements事件处理器不起作用

现在,为了验证之前讨论的内容,我们创建一个新的按钮。jQuery代码如下:
$('#create-button').click(function() {
  if ( $('button.alert').length <2) {
 $('<button class="alert">Not another alert').insertAfter(this);
  }
  return false;
});
你有没有点击上面的超链接创建第二个按钮?如果已经点击了,现在点击新出现的按钮,它正如期望的那样什么也没发生。

对新加的Element CSS依然是工作的

现在我们来看下面的另一个例子。这个例子中,我们有三个列表的项,两个简单项,一个带有class等于”special”的项。
<ul id="list1" class="eventlist">
  <li>plain</li>
  <li class="special">special <button>I am special</button></li>
  <li>plain</li>
</ul>
点击下面的”I am special”按钮创建一个项,它的class还是”special”
  • plain
  • special
  • plain
如果你点了,就会发现,和第一个special的li一样,新的项的背景色也是黄色的。CSS没出啥问题。但是点击新创建的”I am new”按钮,和上面的第二个按钮的例子一样,没有alert发生。下面是我们的jQuery代码,这段代码的作用是在id为list1的元素内的li元 素class等于special的内部的button被点击的时候,我们在这个li元素后面插入一个新的li元素,新的li元素的class等于 special。
$(document).ready(function() {
  $('#list1 li.special button').click(function() {
 var $newLi = $('<li class="special">special and new <button>I am new</button></li>');
 $(this).parent().after($newLi);
  });
});
那么我们怎样才能把事件传递到新加入html文档的元素上呢?两个通常的方法是事件委派重新绑定事件处理器。这个第一部分我们主要看事件委派,在第二部分的文章中,我们将探索重新绑定的方法。

事件委派: 让事件拥抱新的Elements

事件委派的大意是将事件绑定到容器元素上,然后根据容器元素内哪个具体的元素被事件命中而进行一些动作。说的有点绕口,来个具体点的例子,假如我们有一个无序的列表元素:<ul id=”list2″> … </ul>.这时候我们不再将click事件绑定到其内部的按钮上–$(‘#list2 li.special button’).click(…),我们将事件绑定到整个包围着的<ul>上。通过神奇的冒泡, 任何对按钮的点击事件也是对包围按钮的列表项的点击,然后事件会冒泡到整个列表,然后是包含列表的DIV层,然后一直冒泡到window对象。因为每次获 得click事件的<ul>都是同一个(我们只在<ul>内部添加内容的情况下),同样的事件处理代码将会作用于所有的 button,不管这些button是何时创建出来的。

使用事件代理的时候,需要传递”event”参数。所有下面的代码我们用.click(event)而不是.click().当然参数的名称不一定要是event我们可以随意命名。我只是想尽可能的用一些能明显表达出意思的标签,这样代码页比较清晰,JavaScript代码特别容易搞晕。下面是目前我们实现的代码:
$(document).ready(function() {
  $('#list2').click(function(event) {
 var $newLi = $('<li class="special">special and new <button>I am new</button></li>');
  });
到目前为止代码和我们之前第一个例子差不多,除了选择器(#list2)和本例中额外的event参数。现在我们需要检测<ul>中哪个具体元素被点击了,是不是那个”special”按钮。如何是,我们就添加一个新的<li>。我们使用event参数的target属性来检测被点击的元素。
$(document).ready(function() {
  $('#list2').click(function(event) {
 var $newLi = $('<li class="special">special and new <button>I am new</button></li>');
 var $tgt = $(event.target);
 if ($tgt.is('button')) {
   $tgt.parent().after($newLi);
 }
 // next 2 lines show that you've clicked on the ul
 var bgc = $(this).css('backgroundColor');
 $(this).css({backgroundColor: bgc == '#ffcccc' || bgc == 'rgb(255, 204, 204)' ? '#ccccff' : '#ffcccc'});
  });
});
第4行将目标元素用jQuery包装成对象并存储在$tgt变量中。
第5行使用jQuery的is(expr)来检查当前选择的元素是否符合条件’button’,
第6行将新的列表项$newli插入到按钮的父元素的最后,来试试看:
  • plain
  • special
  • plain
我加了额外的两行,来演示在按钮上的点击仍然会被认为是对<ul>的点击事件。你能看到在<ul>内部任意地方的点击都将使得<ul>的背景色在粉色和蓝色之间交替变化。
可能这里值得一提的还有jQuery帮你解决了跨浏览器事件参数的问题。如果你要用纯JavaScript和DOM来做这件事情的话,代码看上去应该有这么多:
var list2 = document.getElementById('list2');
list2.onclick = function(e) {
  var e = e || window.event;
  var tgt = e.target || e.srcElement;
  if (tgt.nodeName.toLowerCase() == 'button') {
    // do something
  }
};

正如你看到的,代码有点纠缠。

使用事件代理的另一个巨大的好处

事件代理也是处理一个很大的文档时候避免让用户浏览器挂掉的一个很不错的方法。例如,你有个几千行的表格,想要为用户点击某个单元格的时候处理 各事件,这个情况下你肯定不希望为每个单元格都加上事件处理器。正常的做法是为table元素加上事件处理代器,然后使用event.target来找到 哪个单元格被点击了:
$(document).ready(function() {
  $('table').click(function(event) {
 var $thisCell, $tgt = $(event.target);
 if ($tgt.is('td')) {
   $thisCell = $tgt;
 } else if ($tgt.parents('td').length) {
   $thisCell = $tgt.parents('td:first');
 }
 // now do something with $thisCell
  });
});

注意上面的代码的else块中我考虑了点击单元格内子元素的情况,但这只是事件代理带来巨大性能提升的同时伴随的一个小小的不方便。

下期带来

在本教程的第二部分的,我们将会通过小心放置函数调用位置的方法来将事件传递到新创建的元素上。当然也会检查非绑定的事件和使用命名空间的事件。同时,我希望大家觉得这一部分的教程是有用的。如果我在文章中有任何错误,特别是用辞方面的,请不要迟疑马上指出来。

本文包含的脚本文件: