在JavaScript中,有三种常用的绑定事件的方法:
在DOM元素中直接绑定:οnclick="eventFunction"
在JavaScript代码中绑定:elementObject.onXXX=function(){ // 事件处理代码 }
绑定事件监听函数:addEventListener() 或 attachEvent() (IE)
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
事件捕获(event capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内(从window到目标元素)进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外(从目标元素到window)进行事件传播,直到根节点。
无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播,它就像一跟引线,只有通过引线才能将绑在引线上的鞭炮(事件监听器)引爆,试想一下,如果引线不导火了,那鞭炮就只有一响了!!!
dom标准事件流的触发的先后顺序为:先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后进行事件冒泡。不同的浏览器对此有着不同的实现,IE10及以下(不支持addEventListener)不支持捕获型事件,所以就少了一个事件捕获阶段,IE11、Chrome 、Firefox、Safari等浏览器则同时存在。
事件绑定的方法,如addEventListener:addEventListener(event, listener, useCapture)
event---(事件名称,如click,不带on);
listener---事件监听函数;
useCapture---为true采用事件捕获,若为false采用事件冒泡 ,即为true事件在捕获阶段执行,若为false事件在冒泡阶段执行
事件的eventPhase属性:它返回一个数字,表示当前的事件处于哪个阶段,可能的值:
0:NONE
1:事件流程处于捕获阶段
2:事件流程处于目标阶段
3:事件流程处于冒泡阶段
<body> <div id="parent"> 父元素 <div id="child">子元素</div> </div> <script> var parent=document.getElementById("parent"); var child=document.getElementById("child"); document.body.addEventListener("click", function(e){ console.log("click-body", e.eventPhase); }, true); parent.addEventListener("click", function(e){ console.log("click-parent", e.eventPhase); }, false); child.addEventListener("click", function(e){ console.log("click-child", e.eventPhase); }, true); </script> </body> 点击‘父元素’输出: click-body 1 click-parent 3 点击‘子元素’输出: click-body 1 // 由于body绑定的事件在捕获阶段就执行了,所以最先输出 click-child 2 // 目标阶段 click-parent 3 // 由于parent绑定的事件在冒泡阶段执行,所以最后输出
<body> <div id="parent"> 父元素 <div id="child">子元素</div> </div> <script> var parent=document.getElementById("parent"); var child=document.getElementById("child"); document.body.addEventListener("click", function(e){ console.log("click-body", e.eventPhase); }, false); parent.addEventListener("click", function(e){ console.log("click-parent", e.eventPhase); }, true); child.addEventListener("click", function(e){ console.log("click-child", e.eventPhase); }, false); </script> </body> 点击‘父元素’输出: click-body 1 click-parent 3 点击‘子元素’输出: click-parent 1 // 由于parent绑定的事件在捕获阶段就执行了,所以最先输出 click-child 2 // 目标阶段 click-body 3 // 由于body绑定的事件在冒泡阶段执行,所以最后输出
事件触发顺序是由内到外的,这就是事件冒泡,虽然只点击子元素,但是它的父元素也会触发相应的事件,因为子元素在父元素里面,点击子元素也就相当于变相的点击了父元素。如果点击子元素不想触发父元素的事件怎么办?可以停止事件传播 --- event.stopPropagation。
<body> <div id="parent"> 父元素 <div id="child">子元素</div> </div> <script> var parent=document.getElementById("parent"); var child=document.getElementById("child"); document.body.addEventListener("click", function(e){ console.log("click-body", e.eventPhase); }, true); parent.addEventListener("click", function(e){ console.log("click-parent", e.eventPhase); }, false); child.addEventListener("click",function(e){ console.log("click-child", e.eventPhase); e.stopPropagation(); // 这里加了阻止事件冒泡的方法 },false); </script> </body> 点击‘子元素’输出: click-body 1 click-child 2
<body> <div id="parent"> 父元素 <div id="child">子元素</div> </div> <script> var parent=document.getElementById("parent"); var child=document.getElementById("child"); document.body.addEventListener("click", function(e){ console.log("click-body", e.eventPhase); }, true); parent.addEventListener("click", function(e){ console.log("click-parent", e.eventPhase); }, true); // 这里改为在捕获阶段执行,阻止冒泡的方法就不好使了 child.addEventListener("click",function(e){ console.log("click-child", e.eventPhase); e.stopPropagation(); // 这里加了阻止事件冒泡的方法 },false); </script> </body> 点击‘子元素’输出: click-body 1 click-parent 1 click-child 2
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。
addEventListener可以添加多个事件处理程序,按照添加它们的顺序进行触发。
var btn = document.getElementById("myBtn"); btn.addEventListener("click", function(){ alert(1); }, false); btn.addEventListener("click", function(){ alert("Hello world"); }, false); // 依次alert数字1和'Hello world'
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除,通过addEventListener()添加的匿名函数将无法移除。
var btn = document.getElementById("myBtn"); btn.addEventListener("click", funtion(){ alert(this.id); },false); //这里省略了其他代码 btn.removeEventListener("click", funtion(){ //没有用 alert(this.id); },false)
var btn = document.getElementById("myBtn"); var handler = funtion(){ alert(this.id); }; btn.addEventListener("click", handler, false); //这里省略了其他代码 btn.removeEventListener("click", handler, false); //有效
用元素直接绑定事件
元素用on绑定的事件是以冒泡的方式向上传播的
<body> <div> xxxxx<p>hello, <span>world!</span> </div> <script> var div = document.getElementsByTagName("div")[0]; var p = document.getElementsByTagName("p")[0]; var span = document.getElementsByTagName("span")[0]; div.onclick = function (event){ console.log('div'); } p.onclick = function (event){ console.log('p'); } span.onclick = function (event){ console.log('span'); } </script> </body> // 点xxxxx输出div // 点hello依次输出p div // 点world依次输出span p div
解决元素用on绑定的事件是以冒泡的方式向上传播的问题
1、用even.target来识别判断
p.onclick = function (event){ if(event.target 怎样怎样){ console.log('p'); } }
2、调用even.stopPropagation()方法
p.onclick = function (event){ console.log('p'); event.stopPropagation(); }
事件向上传播的用处(其实就是事件委托啦~~~):
举个例子,一个table有1000个row,每个row都要注册一个onclick函数,为每个row注册事件函数写起来容易,但由于循环多和有太多的函数需要内存管理,效率会下降。这个时候就可以写一个单一的绑定事件函数,绑定到row的父元素,所有row的click event都会向上传播到row的父元素事件函数里来接受处理。1000个事件函数需要注册和管理,现在变成一个,效率自然提升很多。而且新添加的元素还会有之前的事件。当删除某个元素时,不用移解绑上面的click事件。
事件委托的缺点:
事件委托基于冒泡,对于不冒泡的事件不支持;
层级过多,冒泡过程中,可能会被某层阻止掉;
理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td;
把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。
on和addEventListener绑定事件的一个区别
on事件会被后边的on事件覆盖
// obj是一个dom对象,下同 // 注册第一个点击事件 obj.onclick(function(){ alert("hello world"); }); // 注册第二个点击事件 obj.onclick(function(){ alert("hello world too"); }); // 最终只会输出:hello world too
addEventListener则不会被覆盖
// 注册第一个点击事件 obj.addEventListener("click",function(){ alert("hello world"); })); // 注册第二个点击事件 obj.addEventListener("click",function(){ alert("hello world too"); })); // 会连续输出: // hello world // hello world too
原文:
https://www.cnblogs.com/jiuyi/p/5460448.html
https://www.cnblogs.com/zhglhtt/articles/3255670.html