一直想写一篇关于事件和监听器的文章,但又怕显得很多余,毕竟这不是什么新知识,而且网上的优秀文章已经很多了。总也有这样的感觉:这部分知识我似乎已经懂了,但有时候也会遇到些问题,虽然可以不求甚解的换个方法解决了问题,但总觉得不对劲,肯定还有哪些地方我没有理解到位。本着对前端工作的严谨态度,我看还是有必要重新再整理一遍。一是自己可以再温习一遍,二是算写给刚刚入门的新手看吧,希望可以为你们带来帮助。
似乎是在很久很久以前,我们就已经接触监听器了,从刚刚开始接触HTML的时候,我们就已经会写onclick了,后来就接触了jquery,知道了还可以这么写$().click()、$().bind()、$.live()。然后在很长一段的时间里,我们就自认为已经掌握了这部分知识,乐此不疲的在项目中时而bind(),时而live(),直到出了问题,才反应过来。javascript事件模型与监听器,似乎没那么简单呀,此事必有蹊跷!所以在此重新将这些知识过一遍。
一、javascript事件模型
什么是事件呢?这还用问,就是鼠标点击啦、移动啦、键盘按下啦等等一系列用户触发的动作。我们可以用相应的事件类型表示他们,click、mousemove、keydown。。。那这有什么蹊跷呢?值得一提的便是一次事件处理的过程:捕获-冒泡机制。
举个例子在说明:HTML代码如下
<style type="text/css">
li{width:100px;background-color:red;}
p{width:50px; background-color:green;}
</style>
<ul>
<li><p>0</p></li>
</ul>
js代码如下:
$('li').click(function(){
alert('li');
});
$('p').click(function(event){
lert('p');
});
当我们点击到li元素时,会弹出“li",这个没错。但当我们点击p元素时,会发现先弹出"p",接着又弹出"li"。不怕笑话,之前我是这么理解的:因为点击p的时候同时点到li了,就像踩到地上一张纸,同时也踩到地了一样。事实上这么理解是一个错误。真正的原因正是事件的冒泡机制,所以正确的解释是:发生在p上的点击事件会冒泡到li,因为li上也绑定了点击事件监听器,捕获到这一事件,所以也执行了它自己的监听函数。
那么如何阻止这种情况呢(因为有时候我们并不想让它冒泡),很简单,如下代码:
$('p').click(function(event){
event.stopPropagation(); //阻止事件冒泡
alert('p'+$(this).index());
});
这下你再点击p的时候,就不会再弹li了。
下面是捕获-冒泡的示意图,当点击了p的时候,javascript引擎会从文档的根节点开始,层层深入,找到触发事件的那个元素,这个过程是捕获,在捕获的过程中,会依次检查每个节点上是否绑定了相应的监听器,如果有,则执行其监听函数,然后事件还会再向上一层一层冒泡,知道文档根节点,这个过程,叫做冒泡,同样,冒泡到的每个节点都检查一遍是否绑定了监听器。原来一次事件是这么复杂的过程。明白了这个过程,将有利于我们处理以后的一些问题。
二、绑定事件监听器的方法汇总
绑定事件监听器,也叫注册监听器,一回事。就是我们预先定义好的处理某个元素被触发某个事件的函数。方式有如下几种:
为了方便我先提前定义一个函数f:
function f(){
alert(1);
}
var p = document.getElementsByTagname('p')[0];
1. 直接写在元素中,作为元素的一个属性,如:<p οnclick="f()">11</p>
2. 传统javascript绑定方式,如:p.onclick = f; //注意这里是没有括号的哦,与上面不同
3. w3c指定的标准方法:p.addEventListener('click',f,false); //三个参数分别是:事件类型、处理函数、是否在捕获阶段处理
4.IE浏览器不支持第三种,在IE下的写法:p.attachEvent('onclick',f); //注意事件类型前面要加on
5.jquery的写法有如下几种:
$('p').bind('click',f); //绑定到具体的这个元素
$('p').click(f); //上面写法的简写,仅此而已
$('p').live('click',f); //将监听器绑定到document对象上,等事件冒泡的时候再处理
$('li').delegate('p','click',f); //可以指定将监听器绑定到那个对象上,不一定是document
live()和delegate()都是使用了事件委托的机制,没有将监听器绑定在具体的某一个元素上,而是绑定在他们的父级节点上。两者都调用了jquery的一个底层一点的方法on(),jquery推荐以后用on()取代掉这两者,原因是使用live()的时候当dom层级较多,会引发一些意外的问题。而且是jquery1.9版本之后也将不再支持live()方法,所以我们以后就用on()来统一使用就可以。
三、理解委托机制,正确使用on()
上面提到了委托机制,到底是个什么情况呢?委托,就是本来自己干的事情拜托别人帮忙干了,本来自己要挨个给元素绑定监听器,现在可以把这个任务交给这些元素的父亲,让他来完成。这下自己就可以省事了,而且呢,当父亲又添了孩子,他自己知道该如何处理,这就是我们可以使用委托机制来为动态生成的节点绑定监听器。
既然用on可以实现bind和live,那不妨就在项目中统一了,就用on()来绑定监听器。所以要明白到底如何代替两者呢。先看on()函数的参数,
on(events,[selector],[data],fn)
$('p').on('click',f); //这是代替bind的写法,直接绑定在了<p>元素上
$('li').on('click','p',f); //这是代替delegate的写法,是指将监听器绑定在<li>元素上,委托它来处理它的子元素的点击事件。
这样,我们便可以轻松放下live()了。
那具体什么情况下用哪种方式呢?我想这并没有绝对的答案,只要你明白了这两种方式的区别,结合你代码中的情况,便可以给出正确做法。例如,你的程序会通过ajax请求给一个容器中动态添加子元素,那这时候绑定到此父容器是很好的选择。如果你的DOM层级较深,而且要监听的元素并不是大批量的,那本直接绑在该元素上是很好的选择。