DOM2和IE的事件传播机制(捕捉,起泡)
当事件发生在某个文档节点上时(即事件目标),目标的事件处理程序就会被触发。此外目标的每个祖先节点也有机会处理该事件。
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" onclick="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>