事件
事件流
、冒泡
、捕获
、事件委托
一.事件概念:js与html的交互是通过事件完成的,即文档或浏览器窗口发生特定交互的瞬间。
二.事件流
1.起源:描述的是从页面接收事件的顺序,IE为冒泡流,netspace为捕获流。
2.捕获
:在DOM2.0里,从外向内执行所有有事件的元素,会先执行最外面(父元素),然后依次向内执行直到执行的那个元素,这就是捕获过程。在这个过程中,事件相应的监听函数是不会被触发的。当某个元素触发事件,顶层对象(document)就会发起一个事件流,沿着DOM节点向着真正事件发生的元素流去,直到捕获它。例如:假设页面只有一个div元素,在该元素上点击,捕获顺序document->html->body->div。
注:i>实际在ie9、sarify、chrome、firefox和opera9.5+都会在捕获阶段触发事件。ii>ie9、sarify、chrome、firefox和opera都市从window对象开始捕捉事件的。
目标
:到达事件目标后,若它上面绑定了监听函数,就执行相应函数。若未绑定,则不执行。
冒泡
:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被触发。(事件冒泡是默认存在的),例如:div->body->html->document.
3.事件流顺序:捕获->目标->冒泡。
三.事件的绑定与解除
1.在JavaScript中,有三种常用的绑定事件的方法:
- 在DOM元素中直接绑定;
<btn onclick="show()">点我</button>
我们可以在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等。(详见W3C)
- 在JavaScript代码中绑定;
document.getElementById("btn").onclick=show();
在JavaScript代码中(即script标签内)绑定事件可以使JavaScript代码与HTML标签分离,文档结构清晰,便于管理和开发。但是只能实现一个函数的绑定。
DOM2 绑定事件监听函数
ie下:DOM对象.attachEvent(事件名称(字符串),事件函数);
标准:DOM对象.addEventListener(事件名称,事件函数,usecapture) //默认是false,false:冒泡 true:捕获
浏览器 | 事件 | 是否捕获 | 事件名 | 执行顺序 | this |
---|---|---|---|---|---|
IE | attachEvent | 没 | 有on | 倒序 | Window |
火狐 | addEventListener | 有 | 没有on | 顺序 | 执行该事件的对象 |
注:通过attach添加的事件处理程序,都会被添加到冒泡阶段
2.事件解除
(1)解除绑定
document.onclick = null; //通用
ie:obj.detachEvent(事件名称,事件函数);//ie<=8
标准:obj.removeEventListener(事件名称,事件函数,是否捕获);
(2)通过addEL添加的事件处理程序只能使用removeEL来删除,通过attachE添加的事件只能通过detach来删除,他俩的条件是必须提供相同的参数,也就是说,添加的匿名事件将不能被移除。
3.(跨浏览器)绑定与解除
//封装绑定监听事件
function addEventHandler(target,type,fn){
if(target.addEventListener){
target.addEventListener(type,fn);
}else{
target.attachEvent("on"+type,fn);
}
}
//封装移除监听事件
function removeEventHandler(target,type,fn){
if(target.removeEventListener){
target.removeEventListener(type,fn);
}else{
target.detachEvent("on"+type,fn);
}
}
四。事件对象
(1)DOM中的事件对象
可用属性/方法
cancelbubble 是否可以取消事件的默认行为
currentTarget当前在处理的那个元素
preventDefault 阻止默认事件
detail 事件相关的细节
stopPropagation 取消事件的进一步冒泡或捕获
target 事件的目标
type 被触发的事件类型
(2)IE中事件对象
可用属性/方法
cancelbulle
returnValue (false)阻止默认事件
srcElement 事件的目标
type
注:阻止默认事件
addEventListener //DOM:因为return false只能阻止on事件,故用ev.preventDefault()阻止
attachEvent //ie:用return false
(3)(跨浏览器)阻止默认事件
function fn(ev){
var ev=ev||event;
if(ev.preventDefault){
ev.preventDefault();
}else{
ev.returnValue=false;
}
}
五。事件类型
(1)UI事件–交互时发生
load:加载完触发
reseize:窗口或框架大小变化时,在window或框架上面触发。
scroll:页面滚动时触发
(2)焦点事件
blue:失去焦点时触发
focus:获得焦点时触发
(3)鼠标与滚轮事件
鼠标事件:
mousedown:鼠标的键钮被按下。
mouseup:鼠标的键钮被释放弹起。
click:单击鼠标的键钮。
dblclick:鼠标的键钮被按下。
contextmenu :弹出右键菜单。
mouseover:鼠标移到目标的上方。
mouseout:鼠标移出目标的上方。
mousemove:鼠标在目标的上方移动。
mouseover/out支持冒泡,mouseteener/leave不支持冒泡
(1)不论鼠标指针,穿过被选元素或其子元素,都会触发out/over.
(2)只有在鼠标穿过被选元素时,才会触发teener/leave.
ev.clientX //鼠标指针在客户区的横坐标
ev.pageX //事件在页面的横坐标
ev.screenX //鼠标指针相对于屏幕的横坐标
滚轮事件
类型 | 火狐 | IE/chrome |
---|---|---|
绑定事件 | DOMMouseScroll (addEL) | onmousewheel |
判定方向 | ev.detail (上负,下正) | 上正下负 |
步骤:
1判断浏览器
2选择不同滚轮事件
3根据返回值正负,判断方向
代码封装:
function dowheel( obj, fnUp, fnDown ){
if(navigator.userAgent.indexOf("Firefox")>=0){
obj.addEventListener("DOMMouseScroll",function(ev){
var ev=ev||event;
if(updown(ev)){
fnUp();
}else{
//alert("下");
fnDown();
}
ev.preventDefault();
});
}else{
$("div1").onmousewheel=function(ev){
var ev=ev||event;
if(updown(ev)){
fnUp();
}else{
fnDown();
}
return false;
};
}
function updown(ev){
var b = true;
if(ev.wheelDelta){b=ev.wheelDelta>0?true:false;}
else{b= ev.detail<0?true:false;}
return b;
}
}
//dowheel($("div1"),function fnUp(){alert("上");}, function fnDown(){alert("下");});
(4)键盘与文本事件
keydown:用户按下任意键触发,若连按住不放,则会重复触发此事件。
keypress:用户按下任意字符键触发,若连按住不放,则会重复触发此事件。
keyup:用户释放按键时触发
(5)设备事件
orientationchange事件:通过window.orientationchange获取,共有三个属性值。0:肖像模式(旋转0度),90:左旋转90°的横屏模式,-90:右旋转90°的横屏模式。
(6)触摸与手势事件
touchstart//当手指触摸屏幕时触发
touchmove//手指在屏幕滑动时,连续触发
touched//手指从屏幕移开时触发
touchcancel//系统跟踪时触发
(7)HTML5事件
contextmenu事件:该事件属于鼠标事件,故包含与位置有关的所有属性;该事件冒泡,所以可以为docuemnt指定一个事件处理程序,用以处理页面所有此类事件。注意要清除默认样式,ie用return false;兼容DOM浏览器中,使用ev.preventDefault()。通常利用该事件来显示自定义右键菜单。
六。内存与性能
(一)事件委托
冒泡例子(eg:在三层嵌套div中,从当下事件元素冒泡到父元素,分别弹出123)
HTML:
<div id="div1">
<div id="div2">
<div id="div3"></div>
</div>
</div>
js:
var aDiv = document.getElementsByTagName("div");
for(var i=0;i<aDiv.length;i++){
aDiv[i].onclick = function(){
alert(this.id);
}
}
分析:虽然实现了从事件目标元素到父元素的冒泡,但是假想,若父元素里有大量子元素,难道要给每一个子元素添加事件函数吗?不然,这里引入了事件委托。
1.什么是事件委托?
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。
这里其实还有2层意思的:
第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。
2.优点:
管理的函数变少了。不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件。
可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
JavaScript和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。
3.适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。(所有用到按钮的事件,多数的鼠标事件和键盘事件)
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。
4.如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办?比如说只有点击li才会触发?
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom。
标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,
通过操控target,可以只执行一次DOM操作。
5.现在给一个场景 ul > li > div>p,要求在ul该层点击无任何变化,若在li内点击,则让被点击的那一层变红,如何实现?(利用冒泡)
思路:核心代码是while循环部分,实际上就是一个递归调用,也可以写成一个函数,用递归的方法来调用,同时用到冒泡的原理,从里往外冒泡,知道currentTarget为止,当当前的target是li的时候,就可以执行对应的事件了,然后终止循环
var oLi=document.getElementsByTagName("li")[0];
window.onload=function() {
oLi.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
var oPa = target;//获取目标元素
while (oPa != oLi) {
oPa = oPa.parentNode;
//向上冒泡到li
if (oPa == document) {
break;
}
}
//判断属于li内元素
if (oPa == oLi) {
target.style.background = "red";
}
}
};
(二)移除事件处理程序
例子:设要求点击时,div出现对应内容
<div id="box"></div>
<input type="button" value="click me" id="btn">
<script>
var oBtn=document.getElementById("btn");
oBtn.onclick=function () {
document.getElementById("box").innerHTML="麻烦了...";
}
</script>
当内容展示后,按钮不再需要,此时若直接移除按钮,则按钮对应的事件处理程序极有可能无法被垃圾回收,即事件处理程序与按钮依旧保持着引用关系。当这种引用越多,页面执行就越慢,影响内存与性能。
改进1:移除事件处理程序,即断离引用
<div id="box"></div>
<input type="button" value="click me" id="btn">
<script>
var oBtn=document.getElementById("btn");
oBtn.onclick=function () {
oBtn.onclick=null;
document.getElementById("box").innerHTML="...";
}
</script>
改进2:利用事件委托,通过把事件处理程序给较高层次的元素,可以减少连接数量。
为了内存与性能考虑,使用事件时需要注意:
(1)限制页面中事件处理程序的数量,数量太多会占用内存,页面不灵敏。
(2)使用事件委托,减少事件处理程序的数量
(3)建议在浏览器卸载页面前,移除该页面的所有事件处理程序。