一、事件处理
1.事件绑定
事件绑定是一个比较通俗也比较常用的概念,为某元素添加鼠标点击事件就是最常见的事件绑定
2.事件监听
(1)普通的事件指定方法(单事件监听)
<body>
<div id="e">
test
</div>
</body>
<script type="text/javascript">
window.onload=function(){
document.getElementById("e").onclick=function(){
alert("first");
}
}
</script>
但是我们想为一个标签设定两个响应事件怎么办呢?比如
<script type="text/javascript">
function c1(){
alert("first");
}
function c2(){
alert("second");
}
window.onload=function(){
document.getElementById("e").onclick=c1;
document.getElementById("e").onclick=c2;
}
</script>
这样执行,结果会仅仅触发c2事件,原因是事件c1被覆盖了。所以我们想在一个标签上加两个事件,还要用到专门的监听函数
(2)事件监听函数
监听函数语法:
IE:
attachEvent(type,callback)
type:事件名 如:onclick、onsubmit、onchange等
callback:事件处理程序基于W3C模型的浏览器:
addEventListener(type,callback,capture)
Type:事件名 ,没有“on”前缀 如:click、submit、change
Callback:事件处理程序
Capture:事件模型 (可选参数) (冒泡模型、捕捉模型) true:捕捉模型 false:冒泡模型 (默认值)
IE程序实例:
<script type="text/javascript">
function c1(){
alert("first");
}
function c2(){
alert("second");
}
window.onload=function(){
document.getElementById("e").attachEvent("onclick",c1);
document.getElementById("e").attachEvent("onclick",c2);
}
</script>
W3C:
<script type="text/javascript">
function c1(){
alert("first");
}
function c2(){
alert("second");
}
window.onload=function(){
document.getElementById("e").addEventListener("click",c1);
document.getElementById("e").addEventListener("click",c2);
}
</script>
解决事件监听兼容性问题:
function addEvent(obj,type,callback){
if(window.attachEvent){
obj.attachEvent(type,callback);
}else{
obj.addEventListener(type,callback);
}
}
二、事件流
1.事件模型
来看下面一段代码:
<body>
<div id="div1">
<div id="div2">
<div id="div3">
</div>
</div>
</div>
</body>
事件模型(事件的触发顺序)分为两种:
(1)冒泡模型(2)捕获模型
目前IE只支持冒泡模型
2.事件冒泡
事件冒泡就是指事件响应会一直上升至最顶级元素,看下面一段程序
<head>
<style type="text/css">
div{
border-right: 1px solid black;
border-bottom: 1px solid black;
background-color: gainsboro ;
position: relative;
}
p{
position: absolute;
right: 0;
}
div:hover{
background-color: aquamarine;
}
</style>
</head>
<body>
<div id="div1" style="width: 200px;height: 200px;z-index: 90;">
<p>div1</p>
<div id="div2" style="width: 150px;height: 150px;z-index: 91;">
<p>div2</p>
<div id="div3" style="width: 100px;height: 100px;z-index: 92;">
<p>div3</p>
</div>
</div>
</div>
</body>
当我们将鼠标放至div3标签上时,父标签div2和div3也被点亮,但鼠标放至div3上时却不能点亮div1和div2,这就是冒泡。
再比如
<body>
<div id="div1">
<p>div1</p>
<div id="div2">
<p>div2</p>
<div id="div3">
<p>div3</p>
</div>
</div>
</div>
</body>
<script type="text/javascript">
document.getElementById("div1").addEventListener("click",function(){
alert(1);
},false)
document.getElementById("div2").addEventListener("click",function(){
alert(2);
},false)
document.getElementById("div3").addEventListener("click",function(){
alert(3);
},false)
</script>
点击div1时,只弹出1;
点击div2时,依次弹出2,1;
点击div3时,依次弹出3,2,1
3.事件捕获
将上文中addEventListener中的false改为true便可实现
程序结果:
点击div1时,依次弹出1,2,3;
点击div2时,依次弹出2,3;
点击div3时,只弹出3
4.W3C事件模型执行原理
首先要了解事件处理的三个阶段:事件捕获→目标阶段→事件冒泡阶段
也就是说,任何发生在 w3c事件模型 中的事件,无论是冒泡事件还是捕获事件,元素都会先进入捕获阶段,直到达到目标元素,再进入冒泡阶段。
- 捕获阶段
当我们在 DOM 树的某个节点发生了一些操作(例如单击、鼠标移动上去),就会有一个事件发射过去。这个事件从 Window 发出,不断经过下级节点直到目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。
所有经过的节点,都会触发这个事件。捕获阶段的任务就是建立这个事件传递路线,以便后面冒泡阶段顺着这条路线返回 Window。
监听某个在捕获阶段触发的事件,需要在事件监听函数传递第三个参数 true。
element.addEventListener(<event-name>, <callback>, true);
- 目标阶段(Target Phase)
当事件跑啊跑,跑到了事件触发目标节点那里,最终在目标节点上触发这个事件,就是目标阶段。
需要注意的时,事件触发的目标总是最底层的节点。- 冒泡阶段(Bubbling Phase)
当事件达到目标节点之后,就会沿着原路返回,由于这个过程类似水泡从底部浮到顶部,所以称作冒泡阶段。
仍然以上面的代码为例子,假设目标元素为four,也就是four被点击,执行结果是一样,那么它的执行过程呢?
如下代码:
one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},true);
four.addEventListener('click',function(){
alert('four');
},false);
其实过程就是,从four元素的最顶层的祖先开始向下判断是否有捕获事件的元素,从上往下,如有捕获事件,则执行;一直向下到目标元素后,从目标元素开始向上执行冒泡元素,即第三个参数为false的绑定事件的元素。(在向上执行过程中,已经执行过的捕获事件不再执行,只执行冒泡事件。)
此时点击four元素,four元素为目标元素,one为根元素祖先,从one开始向下判断执行。
one为捕获事件,输出one;
two为冒泡事件,忽略;
three为捕获时间,输出three;
four为目标元素,开始向上冒泡执行,输出four;(从此处分为两部分理解较容易。)
three为捕获已执行,忽略;
two为冒泡事件,输出two;
one为捕获已执行,忽略。
最终执行结果为:one three four two 。简单理解就是先从上到下输出ture的,然后在反过来输出flase的。
(在这里可能会有疑问,目标元素是什么事件有区别吗?我的测试结果是没有区别的,无论目标元素是捕获还是冒泡,在w3c下都是先从根元素执行捕获到目标元素,再从目标元素向上执行。)
有疑问的话可以在这个例子上将其他三个元素作为目标元素测试。
例如,three作为目标元素,执行结果为:one three two(因为two是冒泡事件,在向下执行时没有执行到。)
5.多个事件绑定的执行顺序
多个事件绑定一个元素的情况下,原理也是如此,只不过是在一个元素上触发多个事件,触发顺序也是先捕获后冒泡。
比如
one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two');
},true);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},true);
假如我们点击了three,则会依次弹出one,two,three,two。
大多数情况下,我们需要取消事件冒泡,那如何取消冒泡呢?
IE:
window.event.cancelBubble=true;
W3C:
function(event){
event.stopPropagation();
}
解决兼容性问题:
function stopBubble(e){
if(window.event){
window.event.cancelBubble=true;
}else{
e.stopPropagation();
}
}
三、事件对象
什么是事件对象?
事件对象就是事件发生时系统自动产生的对象,这个对象包含了这个事件发生时所有的信息
如:鼠标移动,那么,鼠标所在的横、纵坐标就保存到了这个事件对象中。
如何获得事件对象?
IE9及以上版本,W3C:
·function(event){}
IE8及以下:
·window.event
默认行为参考:
HTML DOM Event 对象
还有些html元素,有自己的行为,如,提交按钮、超链接
四、事件类型
参考:参考其中的事件句柄
五、事件委托
什么是事件委托?
JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
为什么要用事件委托?
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
事件委托原理:
事件委托是靠事件冒泡模型实现的,简单来说就是事件从最深的节点开始,逐步向上传播事件。
事件委托如何实现?
<ul id="ul0">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
我们要实现点击li实现弹出123,最先想到的办法可能是用for循环给每个li添加点击事件,这样虽然可以实现,但是js操作dom的数量将会由li的个数决定,当li过多时就会导致运行性能不佳,那么我们采用简单的事件委托来解决:
window.onload=function(){
var ul=document.getElementById("ul0");
ul.onclick=function(){
alert(123);
}
}
此时我们将li的父标签ul添加了点击事件,此时我们点击li,根据冒泡模式,点击事件会被传到ul上,此时触发弹窗,问题得到解决,但是还有一个问题,我们点击ul时,同样会触发事件,而我们只想在点击li时触发事件,那么这个问题该怎么解决呢?
window.onload=function(){
var ul=document.getElementById("ul0");
ul.onclick=function(e){
/*
* Event对象给我们提供了一个target属性,
* target 事件属性可返回事件的目标节点(触发该事件的节点)我们称为事件源,
* 也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,
* 当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,
* 此时只是获取了当前节点的位置,并不知道节点名称是什么,这里我们用nodeName来获取具体是什么标签名,
* 这个返回的是一个大写的,我们需要转成小写再做比较
*/
var ev=e||window.event;
var target=ev.target||ev.srcElement;
if(target.nodeName.toLowerCase()=='li'){
alert(123);
}
}
}
上面讲的是所有li触发同样事件的例子,那么各个li触发不同的事件该怎么实现呢?
接着看代码:
<body>
<ul id="ul0">
<li id="a">111</li>
<li id="b">222</li>
<li id="c">333</li>
<li id="d">444</li>
</ul>
<script type="text/javascript">
window.onload=function(){
var ul=document.getElementById("ul0");
ul.onclick=function(e){
var ev=e||window.event;
var target=ev.target||ev.srcElement;
if(target.nodeName.toLowerCase()=='li'){
switch(target.id){
case 'a':alert(111);break;
case 'b':alert(222);break;
case 'c':alert(333);break;
case 'd':alert(444);break;
}
}
}
}
</script>
</body>