Home > Article > Web Front-end > Detailed explanation of event handling in javascript_javascript skills
1. Event propagation mechanism
The client-side JavaScript program (that is, the browser) adopts an asynchronous event-driven programming model. Web browsers generate events when something interesting happens to a document, browser, element, or object related to it. If a JavaScript application cares about a specific type of event, it can register one or more functions to be called when such events occur. Of course, this style is not unique to Web programming; all applications that use graphical user interfaces adopt it.
Since we want to explain event processing in detail, let’s start with a few basic concepts:
①Event type: is a string used to describe what type of event occurred. For example, "mousemove" means the user moved the mouse, and "keydown" means a key on the keyboard was pressed. The event type is just a string, sometimes called the event name;
②Event target: is an object where an event occurs or is related to it. Window, Document, and Element objects are the most common event targets. Of course, the XMLHttpRequest object in AJAX is also an event target;
③Event handler: is a function that processes or responds to events. It is also called an event listener. Applications register their event handlers with Web browsers by specifying the event type and event target.
④Event object: is an object related to a specific event and containing detailed information about the event. The event object is passed to the event processing function as a parameter (but in IE8 and previous versions, the global variable event is the event object). Event objects have a type attribute that specifies the event type and a target attribute that specifies the event target (but in IE8 and previous versions, srcElement is used instead of target). Of course, different types of events will also define some other unique properties for their related event objects. For example, a related object for a mouse event would contain the coordinates of the mouse pointer, while a related object for a keyboard event would contain details of the pressed key and modifier key.
The above completes the four basic concepts. So here comes the question - if you use the mouse to click on a sub-element b of element a on a web page, should the event handler registered for sub-element b be executed first or the event handler registered for element a be executed first (assuming Element a and its child element b both have registered event handlers)? As a reader, have you ever thought about this question?
This problem involves the event propagation mechanism in the browser. I believe everyone has heard of event bubble and event capturing! Yes, they are the event propagation mechanism in browsers. No pictures, no truth, no accompanying pictures? So how can you be generous:
After reading the picture, I believe you have a rough understanding of the event propagation mechanism in the browser: when an event occurs, it will first be passed all the way down from the browser's top-level object Window to the one that triggered the event. Element, this is the event capture process. However, everything is not over yet. The event is passed from this element all the way up to the Window object, which is the event bubbling process (but in IE8 and its previous versions, the event model does not define a capture process, only a bubbling process).
So, regarding the above question, it depends on whether the event handler registered by element a is in the capturing process or the bubbling process. So what exactly is registering an event handler in the capture process, and how to register an event handler in the bubbling process? Now let’s talk about several ways to register event handlers:
1. Set HTML tag attributes as event handlers
The event handler attribute of the document element, its name consists of "on" followed by the event name, for example: onclick, onmouseover. Of course, this form can only register event handlers for DOM elements. Example:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow:hidden;} #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;} #div3{margin:50px auto; width: 100px; height: 100px; background: blue;} </style> </head> <body> <div id="div1" onClick="console.log('div1');">div1 <div id="div2" oNClick="console.log('div2');">div2 <div id="div3" onclick="console.log('div3');" onclick="console.log('div3333');">div3 </div> </div> </div> <script type="text/javascript"> </script> </body> </html>
结果(鼠标点击div3区域后):
从结果中可以看出:
①因为HTML里面不区分大小写,所以这里事件处理程序属性名大写、小写、大小混写均可,属性值就是相应事件处理程序的JavaScript代码;
②若给同一元素写多个onclick事件处理属性,浏览器只执行第一个onclick里面的代码,后面的会被忽略;
③这种形式是在事件冒泡过程中注册事件处理程序的;
2.设置JavaScript对象属性为事件处理程序
可以通过设置某一事件目标的事件处理程序属性来为其注册相应的事件处理程序。事件处理程序属性名字由“on”后面跟着事件名组成,例如:onclick、onmouseover。实例:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow:hidden;} #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;} #div3{margin:50px auto; width: 100px; height: 100px; background: blue;} </style> </head> <body> <div id="div1">div1 <div id="div2">div2 <div id="div3">div3 </div> </div> </div> <script type="text/javascript"> var div1 = document.getElementById('div1'); var div2 = document.getElementById('div2'); var div3 = document.getElementById('div3'); div1.onclick = function(){ console.log('div1'); }; div2.onclick = function(){ console.log('div2'); }; div3.onclick = function(){ console.log('div3'); }; div1.onclick = function(){ console.log('div11111'); }; div1.onClick = function(){ console.log('DIV11111'); }; </script> </body> </html>
结果(鼠标点击div3区域后):
从结果中可以看出:
①因为JavaScript是严格区分大小写的,所以,这种形式下属性名只能按规定小写;
②若给同一元素对象写多个onclick事件处理属性,后面写的会覆盖前面的(ps:这就是在修改一个对象属性的值,属性的值是唯一确定的);
③这种形式也是在事件冒泡过程中注册事件处理程序的;
3.addEventListener()
前两种方式出现在Web初期,众多浏览器都有实现。而addEventListener()方法是标准事件模型中定义的。任何能成为事件目标的对象——这些对象包括Window对象、Document对象和所有文档元素等——都定义了一个名叫addEventListener()的方法,使用这个方法可以为事件目标注册事件处理程序。addEventListener()接受三个参数:第一个参数是要注册处理程序的事件类型,其值是字符串,但并不包括前缀“on”;第二个参数是指当指定类型的事件发生时应该调用的函数;第三个参数是布尔值,其可以忽略(某些旧的浏览器上不能忽略这个参数),默认值为false。这种情况是在事件冒泡过程中注册事件处理程序。当其为true时,就是在事件捕获过程中注册事件处理程序。实例:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow:hidden;} #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;} #div3{margin:50px auto; width: 100px; height: 100px; background: blue;} </style> </head> <body> <div id="div1">div1 <div id="div2">div2 <div id="div3">div3 </div> </div> </div> <script type="text/javascript"> var div1 = document.getElementById('div1'); var div2 = document.getElementById('div2'); var div3 = document.getElementById('div3'); div1.addEventListener('click', function(){ console.log('div1-bubble'); }, false); div2.addEventListener('click', function(){ console.log('div2-bubble'); }, false); div3.addEventListener('click', function(){ console.log('div3-bubble'); }, false); div3.addEventListener('click', function(){ console.log('div3-bubble222'); }, false); div1.addEventListener('click', function(){ console.log('div1-capturing'); }, true); div2.addEventListener('click', function(){ console.log('div2-capturing'); }, true); div3.addEventListener('click', function(){ console.log('div3-capturing'); }, true); </script> </body> </html>
结果(鼠标点击div3区域后):
从结果中可以看出:
①addEventListener()第三个参数的作用正如上面所说;
②通过addEventListener()方法给同一对象注册多个同类型的事件,并不会发生忽略或覆盖,而是会按顺序依次执行;
相对addEventListener()的是removeEventListener()方法,它同样有三个参数,前两个参数自然跟addEventListener()的意义一样,而第三个参数也只需跟相应的addEventListener()的第三个参数保持一致即可,同样可以省略,默认值为false。它表示从对象中删除某个事件处理函数。实例:
div1.addEventListener('click', div1BubbleFun, false); div1.removeEventListener('click', div1BubbleFun, false); function div1BubbleFun(){ console.log('div1-bubble'); }
4.attachEvent()
但是,IE8以及其之前版本的浏览器并不支持addEventListener()和removeEventListener()。相应的,IE定义了类似的方法attachEvent()和detachEvent()。因为IE8以及其之前版本浏览器也不支持事件捕获,所以attachEvent()并不能注册捕获过程中的事件处理函数,因此attachEvent()和detachEvent()要求只有两个参数:事件类型和事件处理函数。而且,它们的第一个参数使用了带“on”前缀的事件处理程序属性名。实例:
var div1 = document.getElementById('div1'); div1.attachEvent('onclick', div1BubbleFun); function div1BubbleFun(){ console.log('div1-bubble'); }
相应的,从对象上删除事件处理程序函数使用detachEvent()。例如:
div1.detachEvent('onclick', div1BubbleFun);
到此为止,我们已经说了浏览器中事件传播机制以及各种注册事件处理程序的方法。下面我们就再说说事件处理程序调用时的一些问题吧!
二.事件处理程序的调用
1.事件处理程序的参数:正如前面所说,通常事件对象作为参数传递给事件处理函数,但IE8以及其之前版本的浏览器中全局变量event才是事件对象。所以,我们在写相关代码时应该注意兼容性问题。实例(给页面上id为div1的元素添加点击事件,当点击该元素时在控制台输出事件类型和被点击元素本身):
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow: hidden;} </style> </head> <body> <div id="div1">div1</div> <script type="text/javascript"> var div1 = document.getElementById('div1'); if(div1.addEventListener){ div1.addEventListener('click', div1Fun, false); }else if(div1.attachEvent){ div1.attachEvent('onclick', div1Fun); } function div1Fun(event){ event = event || window.event; var target = event.target || event.srcElement; console.log(event.type); console.log(target); } </script> </body> </html>
2.事件处理程序的运行环境:关于事件处理程序的运行环境,也就是在事件处理程序中调用上下文(this值)的指向问题,可以看下面四个实例。
实例一:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow: hidden;} </style> </head> <body> <div id="div1" onclick="console.log('html:'); console.log(this);">div1</div> <script type="text/javascript"> </script> </body> </html>
结果一:
从结果可以看出:
①第一种方法事件处理程序中this指向这个元素本身;
实例二:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow: hidden;} </style> </head> <body> <div id="div1" onclick="console.log('html:'); console.log(this);">div1</div> <script type="text/javascript"> var div1 = document.getElementById('div1'); div1.onclick = function(){ console.log('div1.onclick:'); console.log(this); }; </script> </body> </html>
结果二:
从结果可以看出:
①第二种方法事件处理程序中this也指向这个元素本身;
②存在第二种方法时,它会覆盖第一种方法注册的事件处理程序;
实例三:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow: hidden;} </style> </head> <body> <div id="div1" onclick="console.log('html:'); console.log(this);">div1</div> <script type="text/javascript"> var div1 = document.getElementById('div1'); div1.onclick = function(){ console.log('div1.onclick:'); console.log(this); }; div1.addEventListener('click', function(){ console.log('div1.addEventListener:'); console.log(this); }, false); </script> </body> </html>
结果三:
从结果可以看出:
①第三种方法事件处理程序中this也指向这个元素本身;
②第三种方法并不会覆盖第一种或第二种方法注册的事件处理程序;
实例四:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow: hidden;} </style> </head> <body> <div id="div1" onclick="console.log('html:'); console.log(this);">div1</div> <script type="text/javascript"> var div1 = document.getElementById('div1'); div1.onclick = function(){ console.log('div1.onclick:'); console.log(this); }; div1.attachEvent('onclick', function(){ console.log('div1.attachEvent:'); console.log(this === window); }); </script> </body> </html>
结果四:
从结果可以看出:
①第四种方法事件处理程序中this指向全局对象Window;
②第四种方法也不会覆盖第一种或第二种方法注册的事件处理程序;
3.事件处理程序的调用顺序:多个事件处理程序调用规则如下:
①通过HTML属性注册的处理程序和通过设置对象属性的处理程序一直优先调用;
②使用addEventListener()注册的处理程序按照它们的注册顺序依次调用;
③使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序;
4.事件取消:
①取消事件的浏览器默认操作(比如点击超链接元素会自动发生页面跳转的默认操作):如果使用前两种方法注册事件处理程序,可以在处理程序中添加返回值false来取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也可以通过调用事件对象的preventDefault()方法取消事件的默认操作。至于IE8及其之前的浏览器可以通过设置事件对象的returnValue属性为false来取消事件的默认操作。参考代码:
function cancelHandler(event){ var event = event || window.event; if(event.preventDefault){ event.preventDefault(); } if(event.returnValue){ event.returnValue = false; } return false; }
②取消事件传播:在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法阻止事件的继续传播,它能工作在事件传播期间的任何阶段(捕获期阶段、事件目标本身、冒泡阶段);但是在IE8以及其之前版本的浏览器中并不支持stopPropagation()方法,而且这些浏览器也不支持事件传播的捕获阶段,相应的,IE事件对象有一个cancelBubble属性,设置这个属性为true能阻止事件进一步传播(即阻止其冒泡)。参考代码(阻止发生在div3区域的点击事件冒泡到div2和div1):
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>test</title> <style type="text/css"> #div1{width: 300px; height: 300px; background: red; overflow:hidden;} #div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;} #div3{margin:50px auto; width: 100px; height: 100px; background: blue;} </style> </head> <body> <div id="div1">div1 <div id="div2">div2 <div id="div3">div3 </div> </div> </div> <script type="text/javascript"> var div1 = document.getElementById('div1'); var div2 = document.getElementById('div2'); var div3 = document.getElementById('div3'); div1.onclick = function(){ console.log('div1'); }; div2.onclick = function(){ console.log('div2'); }; div3.onclick = function(event){ stopEventPropagation(event); console.log('div3'); }; function stopEventPropagation(event){ var event = event || window.event; if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } </script> </body> </html>
The above is the complete introduction to JavaScript event processing. Of course, there is still something to be exploited about event bubbling. This is what we often call event proxy or event delegation. This knowledge will be updated in the future.