事件传播
为了说明不可单击的页面元素处理单击事件的能力,例如样式转换器中包含按钮的div元素或者兄弟元素h3,我们来实现一个鼠标指针进入元素和离开元素时的效果,首先需要添加一种翻转状态,表明能与鼠标进行某种交互,然后我们.hover()方法给h3元素添加这个样式,伪代码如下:
<style> .hover { cursor: pointer; background-color: #afa; } </style <script> $(document).ready(function() { $('#switcher h3').hover(function() { $(this).addClass('hover'); }, function() { $(this).removeClass('hover'); }); }); </sctipt> <body> ... </body>
注:.hover()方法接受两个函数参数。第一个函数会在鼠标指针进入被选择的元素时执行,而第二个函数会在鼠标指针离开该元素时触发
- 事件的旅程
当页面上发生一个事件时,每个层次上的DOM元素都有机会处理这个事件。以下面的页面模型为例:
1 <div class="foo"> 2 <span class="bar"> 3 <a href="http://www.example.com/"> 4 The quick brown fox jumps over the lazy dog. 5 </a> 6 </span> 7 <p> 8 How razorback-jumping frogs can level six piqued gymnasts! 9 </p> 10 </div>
允许多个元素响应单击事件的一种策略叫做事件捕获。在事件捕获的过程中,事件首先会交给最外层的元素,接着再交给更具体的元素。在这个例子中,意味着单击事件首先会传递给<div>,然后是<span>,最后是<a>。另一种相反的策略叫做事件冒泡。即当事件发生时,会首先发送给最具体的元素,在这个元素获得响应机会之后,事件会向上冒泡到更一般的元素。在我们的例子中,<a>会首先处理事件,然后按照顺序依次是<span>和<div>
- 事件冒泡的副作用
假设在我们的例子中,为<div>添加了一个mouseout事件处理程序。当用户的鼠标指针退出这个<div>时,会按照预期运行mouseout处理程序。因为这个过程发生在顶层元素上,所以其他元素不会取得这个事件。但是,当指针从<a>元素上离开时,<a>元素也会取得一个mouseout事件。然后,这个事件会向上冒泡到<span>和<div>,从而触发上述的事件处理程序。这种冒泡序列很可能不是我们所希望的
通过事件对象改变事件的旅程
假设我们希望增大触发样式转换器折叠或扩展的可单击区域。一种方案就是将事件处理程序从标签移至包含它的<div>元素
$(document).ready(function() { $('#switcher').click(function() { $('#switcher button').toggleClass('hidden'); }); });
完整代码如下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <style type="text/css"> body.large { font-size: 1.5em; } body.narrow { width: 250px; } .selected { font-weight: bold; } .hover { cursor: pointer; background-color: #afa; } .hidden { display:none; } </style> <script src="Scripts/jquery-1.8.2.js"></script> <script src="Scripts/jquery-1.8.2.min.js"></script> <script> $(document).ready(function () { $('#switcher-default').addClass('selected'); $('#switcher button').on('click', function () { var bodyClass = this.id.split('-')[1]; var bodyClass = this.id.split('-')[1]; $('body').removeClass().addClass(bodyClass); $('#switcher button').removeClass('selected'); $(this).addClass('selected'); //eventobj.stopPropagation(); //阻止事件冒泡 //return false; //也可以阻止事件冒泡,也可以阻止默认事件 }); }); $(document).ready(function () { $('#switcher h3').hover(function () { $(this).addClass('hover'); }, function () { $(this).removeClass('hover'); }); }); $(document).ready(function () { $('#switcher').click(function () { $('#switcher button').toggleClass('hidden'); }); }); </script> </head> <body> <div id="switcher" class="switcher"> <h3>样式转换器</h3> <button id="switcher-default"> 默认视图 </button> <button id="switcher-narrow"> 窄列视图 </button> <button id="switcher-large"> 大字视图 </button> </div> <h2>Shakespeare's Plays</h2> <table> <tr> <td>As You Like It</td> <td>Comedy</td> <td></td> </tr> <tr> <td>All's Well that Ends Well</td> <td>Comedy</td> <td>1601</td> </tr> <tr> <td>Hamlet</td> <td>Tragedy</td> <td>1604</td> </tr> <tr> <td>Macbeth</td> <td>Tragedy</td> <td>1606</td> </tr> <tr> <td>Romeo and Juliet</td> <td>Tragedy</td> <td>1595</td> </tr> <tr> <td>Henry IV, Part I</td> <td>History</td> <td>1596</td> </tr> <tr> <td>Henry V</td> <td>History</td> <td>1599</td> </tr> </table> <h2>Shakespeare's Sonnets</h2> <table> <tr> <td>The Fair Youth</td> <td>1–126</td> </tr> <tr> <td>The Dark Lady</td> <td>127–152</td> </tr> <tr> <td>The Rival Poet</td> <td>78–86</td> </tr> </table> </body> </html>
这种改变会使样式转换器的整个区域都可以通过单击切换其可见性。但时也造成了一个问题,即单击按钮会在修改内容区的样式之后折叠样式转换器。导致这个问题的原因就是事件冒泡,即事件首先被按钮处理,然后又沿着DOM树向上传递,直至到达<div id="switcher">激活事件处理程序并隐藏按钮。要解决这个问题,必须访问事件对象
注:事件对象是一种DOM结构,它会在元素获得处理事件的机会时传递给被调用的事件处理程序。这个对象中包含着与事件有关的信息(例如事件发生时的鼠标指针位置),也提供了可以用来影响事件在DOM中传递进程的一些方法
- 事件目标
变量event保存着事件对象。而event.target属性保存着发生事件的目标元素。通过.target,可以确定DOM中首先接收到事件的元素(即实际被单击的元素)。而且,我们知道this引用的是处理事件的DOM元素,修改上面的代码可以得到如下代码
$(document).ready(function() { $('#switcher').click(function(event) { if (event.target == this) { $('#switcher button').toggleClass('hidden'); } }); });
此时的代码确保了被单击的元素是<div id="switcher">,而不是其他后代元素。现在,单击按钮不会再折叠样式转换器,而单击转换器背景区则会触发折叠操作。但是,单击标签(<h3>)同样什么也不会发生,因为它也是一个后代元素
- 停止事件传播
删除刚才的event.target==this,我们来给按钮的事件处理添加一个事件对象并调用event.stopPropagation()方法,这样可以阻止按钮的事件冒泡行为,这样一来,单击按钮的事件会被按钮处理,而且只会被按钮处理。单击样式转换器的其他地方则可以折叠和扩展整个区域,代码如下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <style type="text/css"> body.large { font-size: 1.5em; } body.narrow { width: 250px; } .selected { font-weight: bold; } .hover { cursor: pointer; background-color: #afa; } .hidden { display:none; } </style> <script src="Scripts/jquery-1.8.2.js"></script> <script src="Scripts/jquery-1.8.2.min.js"></script> <script> $(document).ready(function () { $('#switcher-default').addClass('selected'); $('#switcher button').on('click', function (event) { var bodyClass = this.id.split('-')[1]; var bodyClass = this.id.split('-')[1]; $('body').removeClass().addClass(bodyClass); $('#switcher button').removeClass('selected'); $(this).addClass('selected'); event.stopPropagation(); //阻止事件冒泡 //return false; //也可以阻止事件冒泡,也可以阻止默认事件 }); }); $(document).ready(function () { $('#switcher h3').hover(function () { $(this).addClass('hover'); }, function () { $(this).removeClass('hover'); }); }); $(document).ready(function () { $('#switcher').click(function (event) { $('#switcher button').toggleClass('hidden'); }); }); </script> </head> <body> <div id="switcher" class="switcher"> <h3>样式转换器</h3> <button id="switcher-default"> 默认视图 </button> <button id="switcher-narrow"> 窄列视图 </button> <button id="switcher-large"> 大字视图 </button> </div> <h2>Shakespeare's Plays</h2> <table> <tr> <td>As You Like It</td> <td>Comedy</td> <td></td> </tr> <tr> <td>All's Well that Ends Well</td> <td>Comedy</td> <td>1601</td> </tr> <tr> <td>Hamlet</td> <td>Tragedy</td> <td>1604</td> </tr> <tr> <td>Macbeth</td> <td>Tragedy</td> <td>1606</td> </tr> <tr> <td>Romeo and Juliet</td> <td>Tragedy</td> <td>1595</td> </tr> <tr> <td>Henry IV, Part I</td> <td>History</td> <td>1596</td> </tr> <tr> <td>Henry V</td> <td>History</td> <td>1599</td> </tr> </table> <h2>Shakespeare's Sonnets</h2> <table> <tr> <td>The Fair Youth</td> <td>1–126</td> </tr> <tr> <td>The Dark Lady</td> <td>127–152</td> </tr> <tr> <td>The Rival Poet</td> <td>78–86</td> </tr> </table> </body> </html>
- 阻止事件默认行为
对于一个锚元素(<a>),当用户单击时浏览器会加载一个新页面,这种行为与事件处理程序不是同一个概念,它是单击锚元素的默认操作。即便在事件对象上调用.stopPropagation()方法也不能禁止这种默认操作,因为默认操作不是在正常的事件传播流中发生的。在这种情况下,.preventDefault()方法则可以阻止事件的默认行为。另外,如果想要同时停止事件传播和默认操作,可以在事件处理程序中返回false,代码如下:
$(document).ready(function () { $('#switcher-default').addClass('selected'); $('#switcher button').on('click', function (event) { var bodyClass = this.id.split('-')[1]; var bodyClass = this.id.split('-')[1]; $('body').removeClass().addClass(bodyClass); $('#switcher button').removeClass('selected'); $(this).addClass('selected'); //event.stopPropagation(); //阻止事件冒泡 //event.preventDefault(); //阻止默认事件和事件冒泡 return false; //也可以阻止事件冒泡,也可以阻止默认事件 }); });
事件委托
事件冒泡也有好处,通过事件冒泡我们可以用很少的代码为元素注册重复的事件,例如,一个table里有很多td,如果为每个td都注册一个hover事件,不仅需要遍历table的子节点,而且严重降低了性能,通过事件委托可以这样写
$("table").delegate("td", "hover", function(){
$(this).toggleClass("hover"); });
将每个单元格的hover事件委托给了table元素,每当单元格td触发hover事件是就会执行元素table绑定的函数fn