![e50751a1924c0f26d0d57883a95f6cbc.png](https://i-blog.csdnimg.cn/blog_migrate/77b4800d34febedcafee6310a70bb1d1.jpeg)
超全的js事件机制&事件委托,想要理解js事件只需认真看完此篇即可~
目录结构:
什么是事件机制
事件冒泡事件捕获
DOM事件流事件委托
误区
在同一个对象上注册事件,并不一定按照注册顺序执行
event.stopPropagation();就是阻止事件的冒泡return false;阻止默认行为
拓展
stopImmediatePropagation 的使用setCapture 和 releaseCapture
参考文章
什么是事件机制
JavaScript 事件机制描述的是事件在 DOM 里面的传递顺序,以及我们可以对这些事件做出如何的响应。
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
事件捕获(event capturing): 通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
事件冒泡(dubbed bubbling): 与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播,它就像一跟引线,只有通过引线才能将绑在引线上的鞭炮(事件监听器)引爆, 试想一下,如果引线不导火了,那鞭炮就只有一响了!!!
dom标准事件流的触发的先后顺序为 :先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。
不同的浏览器对此有着不同的实现,IE10及以下不支持捕获型事件,所以就少了一个事件捕获阶段,IE11、Chrome 、Firefox、Safari等浏览器则同时存在。
说到事件冒泡与捕获就不得不提一下两个用于事件绑定的方法addEventListener 、
attachEvent 。当然还有其它的事件绑定的方式这里不做介绍。
addEventListener(event, listener, useCapture)
·参数定义:event---(事件名称,如click,不带on), listener---事件监听函数,
useCapture---是否采用事件捕获进行事件捕捉, 默认为false,即采用事件冒泡方式
addEventListener在 IE11、Chrome 、Firefox、Safari等浏览器都得到支持。
attachEvent(event,listener)
·参数定义:event---(事件名称,如onclick,带on), listener---事件监听函数。
attachEvent主要用于IE浏览器,并且仅在IE10及以下才支持,IE11已经废了这个方法了
(微软还是挺识趣的,慢慢向标准靠拢)。
事件冒泡
![70138b113674494ed1d9f8395032be2e.png](https://i-blog.csdnimg.cn/blog_migrate/f700fdf366f77ab8bfabfc4b17c375a6.jpeg)
事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的元素
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>js事件机制</title>
<style>
#parent{
width: 200px; height:200px;
text-align: center; line-height: 3;
background: green;
}
#child{
width: 100px; height: 100px; margin: 0 auto; background: orange;
}
</style>
</head>
<body>
<div id="parent"> 父元素
<div id="child"> 子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent"); var child = document.getElementById("child");
document.body.addEventListener("click",function(e){ console.log("click-body");
},false);
parent.addEventListener("click",function(e){ console.log("click-parent");
},false);
child.addEventListener("click",function(e){ console.log("click-child");
},false);
</script>
</body>
</html>
通过"addEventListener"方法,采用事件冒泡方式给dom元素注册click事件,点击子元素会发生什么呢?如果你对事件冒泡有一定了解的话那你肯定知道上面的代码会输出的顺
序,没错,如下图所示:
![d963ac81f9149381bc587ba6c1edafcf.png](https://i-blog.csdnimg.cn/blog_migrate/ebfe6adbfbb7bf3413dfc4751fc803ab.png)
事件触发顺序是由内到外的,这就是事件冒泡,虽然只点击子元素,但是它的父元素也会触发相应的事件,其实这是合理的,因为子元素在父元素里面,点击子元素也就相当于变相的点击了父元素,这样理解对吧?这里有同学可能要问了,如果点击子元素不想触发父元素的事件怎么办?肯定可以的,那就是停止事件传播---event.stopPropagation();
事件捕获
![73c0a22c4e54177b38cbc0458b1dc81c.png](https://i-blog.csdnimg.cn/blog_migrate/b2c173e56ef71855ef2c791e78d68c65.jpeg)
不太具体的节点更早接受事件,而最具体的元素最后接受事件,和事件冒泡相反修改上面栗子中的代码,给parent元素注册一个捕获事件,如下
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.body.addEventListener("click",function(e){ console.log("click-body");
},false);
parent.addEventListener("click",function(e){ console.log("click-parent---事件传播");
},false);
//新增事件捕获事件代码
parent.addEventListener("click",function(e){ console.log("click-parent--事件捕获");
},true);
child.addEventListener("click",function(e){ console.log("click-child");
},false);
如果你看明白了我前面说的那些,你就知道这个栗子的输出顺序了。
![e337ffc6d072c044b6e09b1f0a920e80.png](https://i-blog.csdnimg.cn/blog_migrate/9afbc064a3bfe5ce7265b7a62b5bf061.png)
父元素通过事件捕获的方式注册了click事件,所以在事件捕获阶段就会触发,然后到了目标阶段,即事件源,之后进行事件传播,parent同时也用冒泡方式注册了click事件,所以这里会触发冒泡事件,最后到根节点。这就是整个事件流程。
DOM事件流
![31cb7c566998d9a90aa7b1c7639edb5d.png](https://i-blog.csdnimg.cn/blog_migrate/154d673e5b4a840cc680abb02f68597a.jpeg)
DOM2级事件规定事件流包括三个阶段,事件捕获阶段,处于目标阶段,时间冒泡阶段, 首先发生的是事件捕获,为截取事件提供机会,然后是实际目标接受事件,最后是冒泡阶段
注:Opera、Firefox、Sarfari都支持DOM事件流,IE不支持事件流,只支持时间冒泡
当一个事件发生以后,它会在不同的DOM节点之间传播(propagation)。这种传播分为三个阶段:
![a7629fbf00be38058176bca3a7de7da2.png](https://i-blog.csdnimg.cn/blog_migrate/97165e54b0997219235a3ebb1870cc61.jpeg)
第一阶段:从window对象传导到目标节点,称为“捕获阶段”(capture phase)。第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
第三阶段:从目标节点传导回window对象,称为“冒泡阶段”(bubbling phase)。
事件委托
事件委托其实也叫事件代理。
定义:事件代理就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。(delegation)。
var ul = document.querySelector('ul'); ul.addEventListener('click', function(event){
if(event.target.tagName.toLowerCase() === 'li'){
//...
}})
上面代码的click 事件的监听函数定义在<ul> 节点,但是实际上,它处理额是子节点<li> 的click事件。这样的好处是,只要定义一个监听函数,就能处理多个子节点的事件,且以后再添加子节点,监听函数依然有效。
那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress 。值得注意的是,mouseover 和mouseout 虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。
更加详细内容可查看:Js 中事件绑定、事件代理和事件委托
误区
在同一个对象上注册事件,并不一定按照注册顺序执行
之所以如此是因为事件目的地节点既绑定了冒泡事件也绑定了捕获事件,此时的执行顺序按照绑定的先后顺序执行(情况比较少见)。
举例
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>js事件机制</title>
<style>
#parent{
width: 200px; height:200px;
text-align: center; line-height: 3; background: green;
}
#child{
width: 100px; height: 100px; margin: 0 auto; background: orange;
}
</style>
</head>
<body>
<div id="parent"> 父元素
<div id="child"> 子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent"); var child = document.getElementById("child");
// document.body.addEventListener("click",function(e){
// console.log("click-body");
// },false); child.addEventListener("click",function(e){ console.log("click-child");
},false);
child.addEventListener("click",function(e){ console.log("click-child-捕获");
},true); parent.addEventListener("click",function(e){
console.log("click-parent");
},false);
parent.addEventListener("click",function(e){ console.log("click-parent-捕获");
},true);
</script>
</body>
</html>
点击子DIV执行结果:
![112d3a6b3f540f78858d894c179ce96b.png](https://i-blog.csdnimg.cn/blog_migrate/54572b1d9f9456c95dcf6995772ac818.png)
交换子div事件,如下:
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>js事件机制</title>
<style>
#parent{
width: 200px; height:200px;
text-align: center; line-height: 3; background: green;
}
#child{
width: 100px; height: 100px; margin: 0 auto; background: orange;
}
</style>
</head>
<body>
<div id="parent"> 父元素
<div id="child"> 子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent"); var child = document.getElementById("child");
// document.body.addEventListener("click",function(e){
// console.log("click-body");
// },false);
child.addEventListener("click",function(e){ console.log("click-child-捕获");
},true);
child.addEventListener("click",function(e){ console.log("click-child");
},false);
parent.addEventListener("click",function(e){ console.log("click-parent");
},false);
parent.addEventListener("click",function(e){ console.log("click-parent-捕获");
},true);
</script>
</body>
</html>
点击子DIV执行结果:
![7a8afbba8d116a43e8a9d2acd6b6a0ac.png](https://i-blog.csdnimg.cn/blog_migrate/d1e9e2cf71b6ac902576a436981e28c9.png)
由于子DIV上绑定了捕获和冒泡事件,所以此时的执行顺序是按照绑定的执行顺序。实际这种情况很少。
event.stopPropagation();就是阻止事件的冒泡
这个表述不能说他错误,但是是不完整的,他除了阻止事件的冒泡,还阻止事件的继续捕获,简而言之就是阻止事件的进一步传播 。下面的例子可以看到:
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>js事件机制</title>
<style>
#parent{
width: 200px; height:200px;
text-align: center; line-height: 3; background: green;
}
#child{
width: 100px; height: 100px; margin: 0 auto; background: orange;
}
</style>
</head>
<body>
<div id="parent"> 父元素
<div id="child"> 子元素
</div>
</div>
<script type="text/javascript">
var parent = document.getElementById("parent"); var child = document.getElementById("child");
child.addEventListener("click",function(e){ console.log("click-child-捕获");
},true);
// child.addEventListener("click",function(e){
// console.log("click-child");
// },false);
// parent.addEventListener("click",function(e){
// console.log("click-parent");
// },false);
parent.addEventListener("click",function(e){ event.stopPropagation();
console.log("click-parent-捕获");
},true);
</script>
</body>
</html>
执行结果:
![38a3794fb76c57f269dda0236d3d6fd1.png](https://i-blog.csdnimg.cn/blog_migrate/7cb51d2970b5d7e2979c3a7e5706041b.png)
return false;阻止默认行为
return false;事件处理过程中,阻止了事件冒泡,也阻止了默认行为(比如刚才它就没有执行超链接的跳转)
return false 不仅阻止了事件往上冒泡,而且阻止了事件本身。event.stopPropagation() 则只阻止事件的进一步传播,不阻止事件本身。
![fcc2cd41c5fd631c3bf002e6b45f5f5f.png](https://i-blog.csdnimg.cn/blog_migrate/341b8396effc26af56176246a857de2b.jpeg)
拓展
stopImmediatePropagation 的使用
这玩意儿是 w3c 的东西,使用的也不是特别多,我们知道 stopPropagation 可以阻止事件的进一步传播,但是他阻止不了该元素上绑定的其他函数的执行,比如我们在 obj 上绑定了 func1 和 func2,如果我们在 func1 中使用了 stopPropagation ,那 func2 依然还是会执行出来。倘若这里使用 stopImmediatePropagation,结果就不一样了,他不仅阻止事件的传播,还阻止 func2 的执行。如:
![418179730c235bdb74a19a6b13745667.png](https://i-blog.csdnimg.cn/blog_migrate/6caa35230efc240abb1f57411870b957.jpeg)
结果是:
![5d6f51cd70c05929b71a1c0e05bece64.png](https://i-blog.csdnimg.cn/blog_migrate/893392e6d9bec90cb9d438538db84f9e.jpeg)
而改成evt.stopImmediatePropagation();之后,阻止了第二个监听事件的触发:
![0810e7379c2bdb1104dcd2c07b1129fa.png](https://i-blog.csdnimg.cn/blog_migrate/dcb2ddc09b4fda71acb4df1e9ea40e64.jpeg)
结果是:
![9d1449e3246eadfe37752f88fc1b59ba.png](https://i-blog.csdnimg.cn/blog_migrate/0d78add8591d2046e4bc39541b3f8cdd.jpeg)
setCapture 和 releaseCapture
这两个是 IE 下的事件绑定函数,只要我们在某个元素上 setCapture 了,那么你在任何地方的鼠标操作(mouseXXX之类的动作)都会在这个元素上触发(前提是你在这个元素上绑定了事件),releaseCapture 或者本窗口失去聚焦才会释放这个绑定~
参考文章
JavaScript 详说事件机制之冒泡、捕获、传播、委托[解惑]JavaScript事件机制
DOM事件传播机制 js中的事件委托或事件代理详解