DOM 事件是 JS 中比较重要的一部分知识,所谓事件,简单理解就是用户对浏览器进行的一个操作。事件在 Web 前端领域有很重要的地位,很多重要的知识点都与事件有关,所以学好 JS 事件可以让我们在JS的学习道路中更进一步。
1、事件流
事件流描述的是从页面中接受事件的顺序。但是 IE 和 Netscape 开发团队提出了两个截然相反的事件流概念,IE 的事件流是事件冒泡流,而 Netscape 的事件流是事件捕获流。
(1)、事件冒泡
事件冒泡,即事件最开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播至最不具体的节点(document)。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件流</title> 6 7 </head> 8 <body> 9 <div> 10 <input type="button" value="按钮"> 11 </div> 12 </body> 13 </html>
上面的实例,在点击按钮之后,click 事件会按照如下的顺序逐级传播:
input -> div -> body -> html -> document
所有现代的浏览器都支持事件冒泡,但是在具体的实现上又有一些差别:
IE5.5 及更早版本中的事件冒泡会跳过 html 元素(从 body 直接跳到 document)。
IE9、Firefox、Chrome 和 Safari 则将事件一直冒泡到 window 对象。
(2)、事件捕获
事件捕获的思想是不太具体的 DOM 节点应该更早接收到事件,而最具体的节点最后接收到事件。
按照事件捕获的思想,上面的实例,click 事件将会以如下的顺序逐级传播:
document -> html -> body -> div -> input
事件捕获和事件冒泡的传播顺序正好是相反的,虽然事件捕获是 Netscape 唯一支持的事件流模型,但 IE9、Safari、Chrome、Opera 和 Firefox 目前也都支持这种事件流模型。
(3)、DOM 事件流
"DOM2级事件" 规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。即同时支持冒泡和捕获,在任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达了最具体的元素,然后,再从最具体的元素向上进行事件冒泡,直到到达 document 。
在 DOM 事件流中,实际的目标在捕获阶段不会接收事件。就是说在捕获阶段,事件从 document 到 html 再到 body 后就停止了。下一个阶段是“处于目标”阶段,于是事件在 div 中发生,并在事件处理中被看成是冒泡阶段的一部分。然后,在冒泡阶段发生。IE8 及更早的版本不支持 DOM 事件流,浏览器在捕获阶段触发事件对象上的事件,结果就是有两个机会在目标对象上面操作事件。
2、事件处理程序
(1)、HTML 事件处理程序
HTML 事件处理程序就是直接把事件添加在 HTML 结构中。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" onclick="alert('Hello')"> 10 </body> 11 </html>
也可以调用在页面中其他地方定义的脚本:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" onclick="show()"> 10 <script> 11 function show(){ 12 alert('Hello'); 13 } 14 </script> 15 </body> 16 </html>
事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。
这样使用会创建一个封装着的元素属性值的函数。这个函数有一个局部变量 event ,也就是事件对象:
<input type="button" value="按钮" onclick="alert(event.type)">
上面的代码返回:click
<input type="button" value="按钮" onclick="alert(event.target)">
上面的代码返回:input 元素
其中,this 值等于事件的目标元素,如:
<input type="button" value="按钮" onclick="alert(this.value)">
上面的代码返回:按钮
所谓的事件对象就是 event,在触发 DOM 上的事件时都会产生一个事件对象。
type:用于获取事件类型。target:用于获取事件目标。
而在 IE8 及更早版本获取事件目标要使用 srcElement 属性代替。
所有现代浏览器引用事件对象,都可以直接使用 event 引用,而在 IE8 及之前版本的浏览器则需要使用 window.event 引用。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" onclick="show()" > 10 <script> 11 function show(ev){ 12 ev = event || window.event; 13 var ele = ev.target || ev.srcElement; 14 alert(ele); 15 } 16 </script> 17 </body> 18 </html>
HTML事件处理程序的缺点:HTML 代码和 JS 代码紧密的耦合在一起,如果需要对事件做出更改,那么 JS 代码和 HTML 代码都需要更改。所以应该使用 JS 指定的事件处理程序。
(2)、JS 传统事件处理程序
JS 中非常传统的方式,就是在一个元素上直接绑定方法,即先获取元素,再以属性方式添加事件。
该方式也叫做 DOM0级 事件处理程序。element.onclick = function(e){}
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" id="btn"> 10 <script> 11 var oBtn = document.getElementById('btn'); 12 oBtn.onclick = function (){ 13 alert('Hello'); 14 } 15 </script> 16 </body> 17 </html>
把一个函数赋值给一个事件处理程序的属性,这是用的比较多的方式,非常简单而且稳定,可适应不同的浏览器,但是这样的事件绑定只会在事件冒泡中运行,捕获不行,且一个事件每次只能绑定一个事件函数。
使用该方式指定的事件处理程序被认为是元素的方法,因此,这时候的事件处理程序是在元素的作用域中运行的,也就是 this 指向当前元素:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>事件</title> </head> <body> <input type="button" value="按钮" id="btn"> <script> var oBtn = document.getElementById('btn'); oBtn.onclick = function (){ alert(this.type); } </script> </body> </html>
上面的代码返回:button
DOM0级 在删除事件时,直接让事件等于空。如:oBtn.onclick=null;
(3)、W3C 事件处理程序
W3C 中推荐使用 addEventListener() 函数进行事件绑定,使用 removeEventListener() 函数删除事件。这种方式也叫做 DOM2级 事件处理程序,它们都接收三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。
通常事件监听都是添加在冒泡阶段,所以添加事件的布尔值为 false。element.addEventListener('click', function (){...}, false)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" id="btn"> 10 <script> 11 var oBtn = document.getElementById('btn'); 12 oBtn.addEventListener('click', function (){ 13 alert('Hello'); 14 }, false); 15 </script> 16 </body> 17 </html>
该方式和前边传统方式绑定事件的效果是一样的,这种方式绑定同时支持冒泡和捕获,addEventListener() 函数最后的参数表达了事件处理的阶段:false(冒泡),true(捕获)。这种方式最重要的好处就是对同一元素的同一个类型事件做绑定不会覆盖,比如上面代码中,再给 oBtn 绑定一个 click 事件,那么两次绑定的事件都会被执行,按照代码的顺序依次执行。
通过 addEventListener() 函数添加的事件只能通过 removeEventListener() 函数删除,在删除事件时,必须传入与添加事件相同的参数。所以,用 addEventListener() 添加的匿名函数将无法移除。
DOM2级 方式和 DOM0级 方式都可以给一个元素添加多个事件,添加的多个事件依次按顺序执行。
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。
(4)、IE 中的事件处理程序
在 IE 浏览器下不支持 addEventListener() 函数,只能在 IE9 以上(包括IE9)可以使用,IE 浏览器相应的要使用 attachEvent() 函数代替。
删除事件则使用 detachEvent() 函数。这两个方法接收相同的两个参数:事件处理程序名称与事件处理函数。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <input type="button" value="按钮" id="btn"> 10 <script> 11 var oBtn = document.getElementById('btn'); 12 oBtn.attachEvent('onclick', function (){ 13 alert('Hello'); 14 }); 15 </script> 16 </body> 17 </html>
attachEvent() 函数支持事件捕获的冒泡阶段,同时它不会覆盖事件的绑定。如果在 Firefox、Chrome 等其他内核的浏览器中使用这个函数去绑定事件,浏览器会报错。
如果需要做跨浏览器的事件处理程序,则需要做兼容性处理,即判断是否支持 attachEvent 方法,如果支持就使用该方法,如果不支持则使用 addEventListener() 方法。
这里需要注意: attachEvent() 的第一个参数是“onclick”,而不是 addEventListener()方法中的“click"。
3、事件流阻止
事件流阻止,这里阻止的是它的继续传播以及有可能产生的默认动作。
在一些情况下我们需要阻止事件流的传播,也就是阻止事件的向上冒泡。
某些事件的对象是可以取消的,这也意味着可以阻止默认动作的发生。
W3C 中定义了两个方法:stopPropagation() 和 preventDefault() 用于阻止事件流。
event.stopPropagation() 方法用于阻止事件冒泡。
event.preventDefault() 方法用于阻止事件的默认行为。
阻止事件的默认行为在做移动端 app 时,用的比较多,比如 a 链接的跳转,不跳转新页面,而是跳转不同的位置。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <div id="box"> 10 <a href="http://www.baidu.com" id="go">跳转</a> 11 </div> 12 <script> 13 var oDiv = document.getElementById('box'); 14 var oA = document.getElementById('go'); 15 oDiv.onclick = function (){ 16 alert('我是a链接的父容器'); 17 } 18 oA.onclick = function (ev){ 19 ev.stopPropagation(); 20 ev.preventDefault(); 21 } 22 </script> 23 </body> 24 </html>
上面的例子中,点击 a 链接后有一个默认的跳转行为,并且浏览器会认为你点了 a 链接,也就点了其父容器 div 元素。阻止了事件冒泡和默认行为之后,再点击跳转,就不会有任何响应了。
stopPropagation() 和 preventDefault() 这两个方法都可以阻止事件,前者是取消事件流的继续冒泡,但是 IE8 及以前的版本不支持该方法,而后者是告诉浏览器不要执行与事件相关联的默认动作,比如 submit 类型的按钮点击会提交。如果直接使用 return false 则表示终止处理函数。
在 IE8 及之前版本的浏览器中没有定义阻止事件流的方法,而是使用属性,cancelBubble 属性值为 true 时取消事件冒泡。returnValue 属性值为 false 时阻止事件的默认行为。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>事件</title> 6 7 </head> 8 <body> 9 <div id="box"> 10 <a href="http://www.baidu.com" id="go">跳转</a> 11 </div> 12 <script> 13 var oDiv = document.getElementById('box'); 14 var oA = document.getElementById('go'); 15 oDiv.onclick = function (){ 16 alert('我是a链接的父容器'); 17 } 18 oA.onclick = function (ev){ 19 ev = event || window.event; 20 if(ev.stopPropagation){ 21 ev.stopPropagation(); 22 } 23 else{ 24 ev.cancelBubble = true; 25 } 26 if(ev.preventDefault){ 27 ev.preventDefault(); 28 } 29 else{ 30 ev.returnValue = false; 31 } 32 } 33 </script> 34 </body> 35 </html>
上面的代码,是兼容性写法,不管是现代的浏览器还是 IE8 及更早版本的浏览器都可以使用。