事件处理和冒泡的讨论
今天又在事件流的讨论上面花了很多时间 ,起因只是因为讨论了 HTML 锚点能否使用比较优雅的方式进行点击操作阻止原先的链接跳转,但是后来又想到了很多问题,和小伟讨论了很久,在这里要感谢他的耐心,很多问题上面我犯2了,自己只是在想没有去动手做,证明自己的想法最简单的途径就是自己动手去实践。
废话不多说了,说说今天的事情吧 … BLA…BLA…BLA… 我原来使用锚点的方式是这样的
<a href="javascript: void(0);" id="back">返回</a>
$(document).delegate("#back", "click", function() {
history.go(-1);
});
Void(0) 的作用是阻止返回,这样就可以保证链接按照原定计划进行跳转了。
Return false 和 event.preventDefault() 的区别
但是这种方式不怎么优雅,所以开始寻找一种优雅的方式来解决锚点的跳转问题。
解决方案 1 :
<a href="# " id="back">返回</a>
$(document).delegate("#back", "click", function() {
history.go(-1);
return false;
});
解决方案 2:
<a href="# " id="back">返回</a>
$(document).delegate("#back", "click", function(event) {
history.go(-1);
event.preventDefault();
});
上述两种方式都可以用来移除锚点的默认跳转行为。但是 return false 和 preventDefault()两种方法的本质全不尽相同,return false 会产生一些意想不到的 ’副作用’ 。
preventDefault() 方法,这里因为使用了 jQuery 的事件触发,event 对象在以前的日志里面曾经说过在这里已经被封装了一层了。所以这里给出 jQuery 的 preventDefault() 方法的解释
Description: If this method is called, the default action of the event will not be triggered.
释义:假如该方法被调用,那么默认的 event 行为将不会被触发。
上面的释义当中的 ’默认行为’ 可以解释为这个事件可能会给环境(浏览器)带来的效果,比如对于 a 标签的点击会导致浏览器进行页面跳转。如果使用了 event. preventDefault() 那么跳转行为就将会被禁止。
而 return false 这里有三个作用
1 event.preventDefault();
2 event.stopPropagation();
3 停止回调函数执行并立即返回。
Return false 显然已经包含了 event. preventDefault() ,而且还产生了 event.stopPropagation() 停止回调函数的执行并且返回 false 这两种副作用。
event.stopPropagation()
这个函数是用来阻止事件冒泡的。同样的给出 jQuery 的说明文档。
Description: Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
释义:阻止事件在 DOM 树上继续冒泡,阻止任何父节点的绑定事件被触发。
这两个函数在做一些复杂的交互的时候会派上比较大的用场,过一段事件我会发一个运用这两个函数制作的下拉菜单的。
上面已经给出了两个函数的说明,这里给出的比较简略,详细的说明信息可以去 jQuery 官网 API 查看。那么在这里很明显我们只是需要阻止 ’锚标签’ a 的的默认链接跳转行为就可以了。所以只需要使用 event.preventDefault() 函数就可以了。Return false 虽然比较简单直接,容易记忆,用起来也很爽但是副作用会很大,在做复杂应用的时候可能会有意想不到的 BUG 出现。
event.stopPropagation()的作用范围
在进行代码优化的时候进行事件委托处理是一个非常常用的方法,在这里简单介绍一下事件委托的原理。
我们都知道函数是对象,而对象会占用内存;内存中的对象越多,性能也就越差。另外 DOM 和 ECMASCRIPT 在进行实现的时候是进行分离的,他们中间有一个收费大桥,使用 JS 对 DOM 的操作越多产生的额外开销也会越大也会影响到页面的性能。所以综合以上两点,假如给每一个元素都绑定上事件处理对象的话无疑会拖慢页面性能。而是用事件委托就可以完美的解决这个问题。
事件委托利用了事件冒泡(event bubbling) 只需要指定一个事件处理程序,就可以干礼某一类型的所有事件。例如, click 事件会一直冒泡到 document 层次,也就是说,我们可以为整个页面指定一个 click 事件处理程序,而不必给每一个可点击的元素分别添加事件处理程序。
而在 jQuery 中就有专门的事件委托函数来进行处理,.delegate() 函数 。
惯例,给出官方说明
.delegate( selector, eventType, handler(eventObject) )Returns: jQuery
Description: Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.
释义:这里直译比较难以理解,所以这里给出我的理解。在父节点上使用 delegate 函数进行事件预先绑定,给符合选择器的元素(现在可能有也可能没有)附加事件处理函数。
这个函数的话如果以前看过我的文章的话应该知道怎么使用的吧,这个就是非常典型的利用事件委托的方式来进行事件绑定了。
绑定的父节点就是事件被委托的节点,所有符合 selector 的子节点的符合类型的事件触发都会被父节点所捕捉到,下面给出一段代码 table 父节点用于捕捉 td 的点击。这样就不用给每一个 td 进行事件绑定了。
<div id="outBox">
<table id="tab">
<tr id="tr">
<td id="td">1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
</table>
</div>
<script type=”text/javascript”>
$("#tab").delegate("tr","click",function(event){
alert("tr click!");
});
$("#tab").delegate("td","click",function(event){
alert("td click!");
});
$("#outBox").click(function(event){
alert("outBox click !");
});
</script>
执行上述代码会有三段 alert 出现分别是
‘td click’
‘tr click’
‘outBox click’。
这样进行事件委托可以节省客观的内存,也可以加快浏览器的速度。
下面结合上面一段代码实验一下 event.stopPropagation() ,
因为这里使用了事件委托会产生这样的疑问,
event.stopPropagation()
冒泡是在实际点击元素节点停止冒泡的呢
还是在委托事件的元素节点冒泡的呢
下面给出实验代码:
<div id="outBox">
<table id="tab">
<tr id="tr">
<td id="td">1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
</table>
</div>
<script type=”text/javascript”>
$("#tab").delegate("tr","click",function(event){
alert("tr click!");
});
$("#tab").delegate("td","click",function(event){
alert("td click!");
event.stopPropagation();
});
$("#outBox").click(function(event){
alert("outBox click !");
});
</script>
实验结果:
点击事件在实际点击节点就停止冒泡了。也就是 event.target 就停止冒泡了。而不是在委托事件的节点停止冒泡的。所以后续节点的事件流就无法被处理了。这个很有意思。(这个说法有点儿问题,我在补充当中写了,抱歉啊。)
2012/07/26 补充
昨天给出的结果有一些问题:
今天又和小伟讨论了下,得出了两点结论:
对于委托元素的子节点(包括自身) preventing any parent handlers from being notified of the event
对于委托元素的父节点 Prevents the event from bubbling up the DOM tree,
昨天的例子是证明了第一点而不是阻止事件冒泡 preventing any parent handlers from being notified of the event只是阻止了委托元素子节点的的点击元素的父节点的事件被探测到(包括自身)。当然这一切都是跟着事件流的顺序来的。
昨天给出了错误的回答很抱歉。
今天再附上一段代码,
<html> <head><title></title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> </head> <body> <div id='superFater' style="background-color:blue; width:300px; height250px; color: white;"> superFater <div id='gradparent' style="background-color:black; width:200px; height200px; color: white;"> gradparent <div id='parent' style="background-color:red; width:100px; height:100px"> parent <div id="son" style="background-color:yellow;color:black;"> son </div> </div> </div> </div> <script> $('#gradparent').on('click','#son', function(e){ alert('son clicked via by gradparent!'); e.stopPropagation(); ;}); $('#gradparent').click(function(){alert('gradparent clicked ');}); $('#parent').click(function(){alert('parent clicked ');}); $('#superFater').click(function(){alert("superFater click !")}); </script> </body> </html>
这段代码的运行结果可以证明两件事件:
1 Event bubbing 事件确实冒泡到了委托元素节点,并且在委托元素节点停止冒泡。
2 在委托元素节点进行事件委托处理的函数一定会在事件冒泡到委托事件元素节点才会被处理,所以 parent click 会比 son click 要快。
下面给出 jQuery 的文档解释,这边写的比较模棱两可 … 一开始我都理解错了。下面结合自己的理解再重新释义一下。
event.stopPropagation()
Description: Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
释义: 阻止事件在(委托元素节点的) DOM TREE 上继续冒泡。阻止(目标节点)的任意父节点的(delegate 绑定)事件被探测到。
THE MOST IMPORTANT THING :
所有的事件都是在事件流的冒泡过程中被触发的,根据事件是如何绑定的他们有着不同的触发顺序,而不是一定会按照 DOM TREE 的结构来进行触发。事件流没有到达节点,所有的相关操作都不会被触发。
1 事件流只有 1 个,点击一次触发一次事件流
2 事件的处理过程实在事件流冒泡的过程中被触发执行的
3 利用事件委托的方式来进行事件绑定的话只有事件流到达委托事件的元素节点才有可能触发事件的处理事件。
4 事件流是可以通过 event.stopPropagation() 停止事件流的冒泡的。