事件流是用来描述页面接收事件的顺序。目前存在的两种基本的事件流为事件冒泡和事件捕获
事件冒泡
即事件应该从嵌套层次最深的节点(目标元素节点)冒泡到较为不具体的节点(Document对象),所有的现代浏览器都支持事件冒泡,但是在IE5.5以及之前的版本事件冒泡会跳过html标签,也就是说会直接从body冒泡到Document,而在IE9,firefox,chrome和safari浏览器则会将事件一直冒泡到window对象上。
<!DOCUMENT html>
<html>
<head>
<title>HTML</title>
</head>
<body>
<p id="click">Click</p>
</body>
</html>
如果点击了p标签的元素,事件就会向上冒泡,依次为<p>,<body>,<html>,<Document>
事件捕获
即事件应该从不具体的节点先接收,然后再向更深层次的节点传播,以上述代码为例,如果是事件捕获,则Document对象会先捕获到事件,然后再沿着DOM树向下到html标签,body标签,最后到达p标签
IE事件流
1、IE事件流只支持事件冒泡,不支持事件捕获
2、IE事件流可以通过可以通过attachEvent()和detachEvent()来添加和删除事件,这两个方法只接受两个参数,依次为 (target,handler),事件类型("on"+事件类型),事件处理函数,需要注意的一点是在使用detachEvent()方法删除事件时,参数要和创建事件时的参数相同,即第二个参数不支持通过匿名函数来创建,否则会被视为不是同一个方法。
const btn = document.getElementById("button");
btn.attachEvent("onclick",function(){
console.log("click");
})
btn.detachEvent("onclick",function(){
console.log("click");
})
//删除失败,尽管两个匿名函数一样,但是在处理时会被识别为两个不同的函数对象
3、IE事件中的this指向的是window对象而不是目标元素对象,是因为在使用attachEvent()创建事件时,事件处理程序运行的作用于不是目标元素所在的作用域,而是在全局作用域中运行,所以this指向的是window而不是目标元素
4、在IE事件流中,如果为一个元素顺序绑定多个事件,事件执行的顺序是按照逆序执行的。
const btn = document.getElementById("button");
btn.attachEvent("onclick",function(){
console.log("one");
})
btn.attachEvent("onclick",function(){
console.log("two");
})
btn.attachEvent("onclick",function(){
console.log("three");
})
//出发事件的运行结果为
//three
//two
//one
5、IE事件中可以通过returnValue属性来阻止事件的默认行为,即将window.event.returnValue设置为false就可以阻止事件的默认行为,可以通过cancelBubble属性来停止事件的进一步传播,由于IE事件流只支持事件冒泡,所以该属性只能停止事件冒泡行为。
W3C事件流
1、W3C事件流支持事件捕获和事件冒泡
2、W3C事件流可以通过可以通过addEventListener()和removeEventListener()来添加和删除事件,这两个方法接受三个参数,依次为 (target,handler,boolean),事件类型,事件处理函数,布尔值(如果设置为false,表示该事件处理函数在事件冒泡阶段触发,如果设置为true,表示该事件处理函数在事件捕获阶段触发,事件冒泡阶段不再触发)需要注意的一点是在使用removeEventListener()方法删除事件时,参数要和创建事件时的参数相同,即第二个参数不支持通过匿名函数来创建,否则会被视为不是同一个方法(同IE事件流规则相同)。
3、W3C事件流中this指向的是目标元素的作用域,不同于IE事件处理程序的作用域为全局作用域,W3C事件流中的事件处理程序的作用域为目标元素所在的作用域,所以this指向的是目标元素。
4、在W3C事件流中,如果为一个元素顺序绑定多个事件,事件执行的顺序是按照顺序执行的。
const btn = document.getElementById("button");
btn.attachEvent("onclick",function(){
console.log("one");
})
btn.attachEvent("onclick",function(){
console.log("two");
})
btn.attachEvent("onclick",function(){
console.log("three");
})
//出发事件的运行结果为
//one
//two
//three
5、W3C事件中可以通过preventDefault()方法阻止事件的默认行为,可以通过stopPropagation()方法停止事件在DOM结构中进行传播,即停止事件继续冒泡或者捕获。
6、还可以通过event对象的eventPhase属性来判断事件处于哪一个阶段,调用event.eventPhase,如果返回值为1,则事件处于捕获阶段,如果返回值为2,则事件处于目标阶段,如果返回值为3,则事件处于冒泡阶段。
跨浏览器事件
var EventUtil = {
add:function(node,target,handler){
if(node.addEventListener){
node.addEventListener(target,handler,false);
}else if(node.attachEvent){
node.attachEvent("on"+target,handler);
}else{
node["on"+target] = handler;
}
},
remove:function(node,target,handler){
if(node.removeEventListener){
node.removeEventListener(target,handler,false);
}else if(node.detachEvent){
node.detachEvent("on"+target,handler);
}else{
node["on"+target] = handler;
}
}
}
const btn = document.getElementById("button");
EventUtil.add(btn,"click",function(){
console.log("click");
});
可以通过调用对象EventUtil中的属性来实现添加事件和删除事件,每个属性中通过if-else 来判断浏览器兼容的事件处理程序实现简单的跨浏览器事件调用。
事件委托
事件委托是为处理过多的事件处理程序而设计的,事件委托采用事件冒泡,也就是说,如果想要为一个元素的所有子节点都添加一个事件处理程序的话,只需要获取该父节点的DOM即可,如果想要冒泡到document,也可以直接在document对象上添加事件,而不需要给每一个子节点一个一个添加事件处理程序,以下以W3C事件为例子
var btn = document.getElementById("button");
btn.addEventListener("click",function(event){
let target = event.target;
switch(target.innerHTML){
case "one":
console.log("one");
break;
case "two":
alert("two");
break;
case:"three":
localtion.href = "www.greatytc.com";
break;
default:
alert("undefined");
},false)
使用事件委托,首先可以减少DOM操作,只需要获取最外层元素的节点即可,其次如果要对每一个元素都一一添加事件,会占用大量的内存,影响整个页面运行的性能,同时通过事件委托也可以提高添加事件的效率,上述实例仅通过switch-case语句就可以实现对于每一个子节点事件的添加,避免了使用一个个addEventListener()或者attachEvent()方法来为每一个子节点添加事件处理程序。