http://peterlij.javaeye.com/blog/709859
当事件发生在某个文档节点上时(即事件目标),目标的事件处理程序就会被触发。此外目标的每个祖先节点也有机会处理该事件。
2级DOM的事件传播包含三个阶段:(最后是摘自百度百科的材料:冒泡事件流)
- 捕捉阶段(capturing),事件从顶级文档树节点一级一级向下遍历,直到到达该事件的目标节点。
- 到达事件的目标节点,执行目标节点的时间处理程序。
- 事件起泡(bubbling),事件从目标节点一级一级向上上溯,直到顶级文档树节点。
相应的,2级DOM通过下面的两个函数给节点对象添加和删除事件处理函数。
addEventListener(eventType, handler, propagate);
removeEventListener(eventType, handler, propagate);
三个参数意思分别如下:
- eventType: 即事件类型(不加on)。比如:"click"。
- handler: 事件处理函数。传入参数即为事件对象event。
- propagate: 是否只执行捕获和目标节点两个阶段。true的话,只执行1,2两个阶段;false的话,只指向2,3两个阶段。
IE的事件传播只包含上边的2和3两个阶段
相应的,IE通过下面两个函数给节点对象添加和删除事件处理函数。
attachEvent(eventType, handler);
detachEvent(eventType, handler);
参数意思同2级DOM对应的函数参数。
下面是程序的部分执行结果:
1. 开启捕捉过程,点击SetEventHandler按钮之后,点击Click Me按钮,观察捕捉过程中的事件响应
2. 开启阻止起泡过程,点击SetEventHandler按钮之后,点击Click Me按钮,观察起泡过程中的事件响应
以下是Sample的代码,分别给window, document, document.body, divOut, divIn和btnClick针对不同的浏览器追加了事件处理函数,并且在画面上列出执行时候的可配置项,动态模拟各种配置项对事件传播过程的影响,同时也将各种情况下的执行结果显示到了页面上。浏览器(Chrome, IE)。
- <html>
- <head>
- <script type="text/javascript">
- var disablePropagation = true;
- var cancelBubble = false;
- //Simple judge of browser
- var isIE = window.attachEvent ? true : false;
- //Short name for document.getElementById
- var byId = function(id) {
- return document.getElementById(id);
- }
- // Has event handler already been set.
- var isEventHandlerSetted = false;
- // value setted for Enable Propagate Checkbox at last time.
- var preisEnablePropagate = false;
- // event happend at last time.
- var preEvent = null;
- // Set Event Handler for elements
- function setEventHandler() {
- clearTrace();
- // remove setted event handlers at last time
- if (isEventHandlerSetted) {
- if (isIE) {
- window.detachEvent('onclick', prtDtlWindow);
- document.detachEvent('onclick', prtDtlDocument);
- document.body.detachEvent('onclick', prtDtlBody);
- byId('divOut').detachEvent('onclick', prtDtlDivOut);
- byId('divIn').detachEvent('onclick', prtDtlDivIn);
- byId('btnClick').detachEvent('onclick', prtDtlClickMeBtn);
- } else {
- window.removeEventListener('click', prtDtl, preisEnablePropagate);
- document.removeEventListener('click', prtDtl, preisEnablePropagate);
- document.body.removeEventListener('click', prtDtl, preisEnablePropagate);
- byId('divOut').removeEventListener('click', prtDtl, preisEnablePropagate);
- byId('divIn').removeEventListener('click', prtDtl, preisEnablePropagate);
- byId('btnClick').removeEventListener('click', prtDtl, preisEnablePropagate);
- }
- }
- // Add new event handlers according to new setting.
- var isEnablePropagate = byId("cbxEnablePropagate").checked;
- preisEnablePropagate = isEnablePropagate;
- if (isIE) {
- window.attachEvent('onclick', prtDtlWindow);
- document.attachEvent('onclick', prtDtlDocument);
- document.body.attachEvent('onclick', prtDtlBody);
- byId('divOut').attachEvent('onclick', prtDtlDivOut);
- byId('divIn').attachEvent('onclick', prtDtlDivIn);
- byId('btnClick').attachEvent('onclick', prtDtlClickMeBtn);
- } else {
- window.addEventListener('click', prtDtl, isEnablePropagate);
- document.addEventListener('click', prtDtl, isEnablePropagate);
- document.body.addEventListener('click', prtDtl, isEnablePropagate);
- byId('divOut').addEventListener('click', prtDtl, isEnablePropagate);
- byId('divIn').addEventListener('click', prtDtl, isEnablePropagate);
- byId('btnClick').addEventListener('click', prtDtl, isEnablePropagate);
- }
- isEventHandlerSetted = true;
- }
- // a series of event handler for IE
- function prtDtlWindow() {
- prtDtl(window.event, window);
- }
- function prtDtlDocument() {
- prtDtl(window.event, document);
- }
- function prtDtlBody() {
- prtDtl(window.event, document.body);
- }
- function prtDtlDivOut() {
- prtDtl(window.event, byId ('divOut'));
- }
- function prtDtlDivIn() {
- prtDtl(window.event, byId ('divIn'));
- }
- function prtDtlClickMeBtn() {
- prtDtl(window.event, byId ('btnClick'));
- }
- // print detail formatted event handler execution info.
- function prtDtl(e, currentTarget) {
- if (!isSameEvent(preEvent, e)) {
- clearTrace();
- }
- var target = null;
- var curTarget = null;
- if (isIE) {
- target = e.srcElement;
- curTarget = currentTarget
- } else {
- target = e.target;
- curTarget = e.currentTarget;
- }
- if (!isSameEvent(preEvent, e))
- trace(target, true);
- trace(curTarget, false);
- if (isIE) {
- /* when I hold e directively ussing preEvent, preEvent's srcElement.id
- is the same as e's srcElement.id even when I click again.
- So I use preEvent to hold e's srcElement.id directively.
- (It seems the preEvent and e always share the same reference, but this
- is conflict with the result [preEvent === e] which returns false.)
- Maybe IE uses a completely different logic when compare Event Object I guess,
- who hnows.*/
- //preEvent = e;
- preEvent = target.id;
- } else {
- preEvent = e;
- }
- var isCclBubble = byId("cbxCclBubble").checked;
- var isEnablePropagate = byId("cbxEnablePropagate").checked;
- if (isCclBubble || isEnablePropagate) {
- stopEvent(e, curTarget);
- }
- }
- function trace(t, isTarget) {
- var targetName = "";
- if (t === window) {
- targetName = "Window";
- } else if (t === document) {
- targetName = "Document";
- } else if (t.getAttribute != null){
- targetName = t.getAttribute('detail');
- } else {
- for (var p in t) {
- if (p.indexof('name') != -1) {
- targetName = p;
- break;
- }
- }
- }
- var traceTxt = byId('traceArea');
- if (isTarget) {
- var browser = "Not IE";
- if (isIE)
- browser = "IE";
- traceTxt.value += ("Your Browser is " + browser + ". /n");
- traceTxt.value += ("Event Target is [" + targetName + "]. /n/n/n");
- } else {
- traceTxt.value += ("[" + targetName + "]'s handling function called. /n");
- }
- }
- function stopEvent(e, curTarget) {
- var stopAtWindow = byId("cbxWindow").checked;
- var stopAtDocument = byId("cbxDocument").checked;
- var stopAtBody = byId("cbxBody").checked;
- var stopAtOutDiv = byId("cbxOutDiv").checked;
- var stopAtInDiv = byId("cbxInDiv").checked;
- var stopAtClickMeBtn = byId("cbxClickMeBtn").checked;
- switch (curTarget) {
- case window:
- stopEventIn(e, stopAtWindow);
- break;
- case document:
- stopEventIn(e, stopAtDocument);
- break;
- case document.body:
- stopEventIn(e, stopAtBody);
- break;
- case byId("divOut"):
- stopEventIn(e, stopAtOutDiv);
- break;
- case byId("divIn"):
- stopEventIn(e, stopAtInDiv);
- break;
- case byId("btnClick"):
- stopEventIn(e, stopAtClickMeBtn);
- break;
- default:
- ;
- }
- }
- function stopEventIn(e, stop) {
- if (!stop)
- return;
- if (isIE) {
- e.cancelBubble = true;
- return;
- }
- e.stopPropagation();
- }
- function clearTrace() {
- byId("traceArea").value = "";
- }
- /* It is strange that IE create several event object for
- different elements' event handler.
- although user only click once. why????? TODO */
- function isSameEvent(preEvent, e) {
- if (preEvent == null) {
- return false;
- }
- if (isIE) {
- /* It seems preEvent and e share same reference because that even
- when i click again and again,
- preEvent.srcElement.id always has the same value with e.srcElement.id */
- // return (preEvent.srcElement.id == e.srcElement.id);
- return (preEvent == e.srcElement.id);
- }
- return preEvent===e;
- }
- </script>
- </head>
- <body detail="Body">
- <table><tr>
- <td>
- <div id="divOut" detail="outter div" style="height:300; width:300; border:1 red solid">
- <div id="divIn" detail="inner div" style="height:200; width:200; border:1 blue solid">
- <button id="btnClick" type="button" detail="click me button">Click Me</button>
- </div>
- </div>
- </td>
- <td>
- <div id="divSet" detail="Setting div" style="height:300; width:300; border:1 red solid">
- <table>
- <tr><td><button type="button" detail="Setting button" οnclick="setEventHandler()">Set EventHandler</button></td></tr>
- <tr><td><label>
- <input id="cbxEnablePropagate" detail="Enable Propagate Checkbox" type="checkbox"/>Enable Propagate ?</label></td></tr>
- <tr><td><label><input id="cbxCclBubble" detail="Cancel Bubble Checkbox" type="checkbox"/>Cancel Bubble ?</label></td></tr>
- <tr><td><label><input id="cbxWindow" detail="Stop At Window Checkbox" type="checkbox"/>Stop At Window ?</label></td></tr>
- <tr><td><label><input id="cbxDocument" detail="Stop At Docuement Checkbox" type="checkbox"/>Stop At Docuement ?</label></td></tr>
- <tr><td><label><input id="cbxBody" detail="Stop At Body Checkbox" type="checkbox"/>Stop At Body ?</label></td></tr>
- <tr><td><label><input id="cbxOutDiv" detail="Stop At Outdiv Checkbox" type="checkbox"/>Stop At OutDiv ?</label></td></tr>
- <tr><td><label><input id="cbxInDiv" detail="Stop At InDiv Checkbox" type="checkbox"/>Stop At InDiv ?</label></td></tr>
- <tr><td><label><input id="cbxClickMeBtn" detail="Stop At Click me Button" type="checkbox"/>Stop At ClickMeBtn ?</label></td></tr>
- </table>
- <div>
- </td>
- <td>
- <div id="divTrace" style="height:300; width:300; border:1 red solid;">
- <textarea id="traceArea" detail="TextArea" style="width:100%; height:100%"></textarea>
- </div>
- </td>
- </tr></table>
- </body>
- </html>
冒泡事件流
事件传播——冒泡与捕获
DOM事件标准定义了两种事件流,这两种事件流有着显著的不同并且可能对你的应用有着相当大的影响。这两种事件流分别是捕获和冒泡。和许多Web技术一样,在它们成为标准之前,Netscape和微软各自不同地实现了它们。Netscape选择实现了捕获事件流,微软则实现了冒泡事件流。幸运的是,W3C决定组合使用这两种方法,并且大多数新浏览器都遵循这两种事件流方式。 默认情况下,事件使用冒泡事件流,不使用捕获事件流。然而,在Firefox和Safari里,你可以显式的指定使用捕获事件流,方法是在注册事件时传入useCapture参数,将这个参数设为true。冒泡事件流
当事件在某一DOM元素被触发时,例如用户在客户名字节点上点击鼠标,事件将跟随着该节点继承自的各个父节点冒泡穿过整个的DOM节点层次,直到它遇到依附有该事件类型处理器的节点,此时,该事件是onclick事件。在冒泡过程中的任何时候都可以终止事件的冒泡,在遵从W3C标准的浏览器里可以通过调用事件对象上的stopPropagation()方法,在Internet Explorer里可以通过设置事件对象的cancelBubble属性为true。如果不停止事件的传播,事件将一直通过DOM冒泡直至到达文档根。捕获事件流
事件的处理将从DOM层次的根开始,而不是从触发事件的目标元素开始,事件被从目标元素的所有祖先元素依次往下传递。在这个过程中,事件会被从文档根到事件目标元素之间各个继承派生的元素所捕获,如果事件监听器在被注册时设置了useCapture属性为true,那么它们可以被分派给这期间的任何元素以对事件做出处理;否则,事件会被接着传递给派生元素路径上的下一元素,直至目标元素。事件到达目标元素后,它会接着通过DOM节点再进行冒泡。