浏览器中的事件流意味着页面上可有不仅一个,甚至多个元素响应同一个事件。而这一个或多个元素响应事件发生的先后顺序在各个浏览器(主要针对IE和Netscape)上是不同的。
IE上的解决方案就是冒泡型事件(Dubbed Bubbling)。冒泡型事件的基本思想是,事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。
示例(1):点击我触发冒泡型事件流
示例(1)的XHTML代码结构:<br><span id="cnt0"><br> <a href=”#1″ id=”link0″>点击我触发冒泡型事件流</a><br></span><br>
这个示例里我同时给span和a标签绑定了click事件,大家看到了,我们点击这个a标签,一次点击(click)同时触发了两个元素a和span的事件。先触发的是绑定给a标签的click事件,然后触发的是span标签的click事件。也就是前面提到的“页面上可有不仅一个,甚至多个元素响应同一个事件”和“事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发”。
这个示例里我们点击的第一目标是a标签这个链接,它就是前面提到的“最特定的事件目标”,然后才是span这个相对“不特定的事件目标”。再看看我的XHTML代码结构,你会发现a标签包含在span标签中,也就是说span是a标签的父节点,如果大家还不是很清晰的知道他们之间的关系,让我们看看下面的这个DOM树的结构图:
事件触发的顺序是从最特定的目标,沿着DOM树不断的向上触发click事件,就像气泡从下一直上冒的过程一样。“冒泡型”也就是这么得来的,也很形象。这里要说明的是,由于我只给a和span绑定了click事件,所以“冒”到span就到顶了,如果你也给包含他们的p标签还有document绑定click事件,这个冒泡的过程就会一直延续到document的事件触发才结束。
另外要说明的是,在IE5.5中冒泡的最高层DOM节点为document,IE6中则可以支持html节点。在Mozllia1.0及之后的版本也支持冒泡,而它则更可以冒到window窗口对象。
相对IE4.0,Netscape4.0则使用的是捕获型事件的解决方案。这个事件触发的过程则正好和冒泡相反——在捕获型事件中,事件从最不精确的对象(document对象)开始触发,然后到最精确的对象。还是前面的示例,不过现在换由捕获型事件触发(当然你需要在Netscape或Firefox中测试)。
示例(2):点击我触发捕获型事件流
示例(2)的XHTML代码结构:<br><span id="cnt1"><br> <a href=”#1″ id=”link1″>点击我触发捕获型事件流</a><br></span><br>
事件触发的循序正好跟前面的冒泡相反,这里我就不赘述了。
这个事件流则是W3C制定一个标准规范,它同时支持两种事件流模式,不过是先发生捕获型事件流,再发生冒泡型事件流。
DOM事件流最独特的是,它支持文本节点也触发事件(IE中这不支持)。不过说实话,我现在还看不出来让文本节点支持事件有什么作用。
最后要说的是,根据最近大家在开发的实践过程中的运用,我们一般都采取冒泡型的事件流触发方式,这点我们的IE做的比较成功。至于原因,我想你可以通过上面的解释可以看出,毕竟我们给元素触发事件,肯定是希望从我们最希望先触发(从最精确的)的那个开始。而DOM的先捕获后冒泡我觉得只能用让我很疑惑来形容我的感受。因为如果按照DOM的事件流,我们的事件要被触发2次,而我们一般都只会选择一个类型的事件流,值希望触发一次,很难理解当初W3C是怎么想的?!一个字——晕!可能我的理解能力有限,不过这是我的真是感受。
这里我不想做过多的介绍,我们知道在IE里使用attachEvent(”NAME_OF_EVENT_HANDLER”, fnHandler)给元素绑定事件,而在支持DOM事件流的浏览器里,则使用addEventListener(”NAME_OF_EVENT_HANDLER”, fnHandler, isCapture)。前面我控制FIREFOX中触发捕获事件流,就是通过设置isCapture(ture:捕获;false:冒泡)做到的。还有就是我们传统element.onclick或者element['on'+eventName],这个是所有浏览器都支持的事件绑定的监听器,而且我测试的结果XP下的IE6~8、Firefox2.0~3.5、Safari4.0、Opera9.6.4、Chrome2.0.180都是以冒泡的事件流触发的。更老的浏览器我就没有测试过了,不过根据《Pro Javascript Techniques》里介绍,老的浏览器使用onclick这样的事件绑定,触发的也是冒泡事件流。如果你有兴趣可以淘下那些古董浏览器,测试一番。不过还是有不支持冒泡的事件的,下面我们就讲讲。
这个是比较有意思的,这里的总结都来自PPK在YAHOO的演讲《Javascript Event》(推荐大家一定看看,很经典!),简单总结PPK的内容,基本上只有onload、unload、focus、blur、submit和change事件是不支持冒泡的,这也是我在前面说“一般使用冒泡事件流”。自然向keydown、keypress、keyup、click、dbclick、mousedown、mouseout、mouseover、mouseup、mousemove。用PPK的话说,那就是“Mouse and Key Events”支持冒泡,而Interface Events(也就是《Javascript高级程序设计》里的HTML(HTML是来构建interface的)事件。)则只支持捕获。
他又说了下是,click是“最安全的”事件,它即支持冒泡,又支持捕获。鼠标事件可以触发click,键盘事件也可以触发click。还有就是在支持DOM事件的浏览器里,focus和blur是只支持捕获的,所以如果你如果用到我下面给出的addEvent函数,在给元素绑定focus或则blur事件时,bCapture一定要设置为true。那么这里就发生了一个问题,IE是不支持捕获的,那不是触发不了这连个事件?呵呵,是个严重的问题哦?不过在IE里使用focus和blur事件的时候,其实触发的是IE的focusin和focusout,当然这两个事件也是只支持冒泡的。
PPK虽然这么说,但是我还是想实践一下,于是我这里这么处理了下,window.onfocus = function(){alert('ok')},lnkOne.onfocus = demoClick;有趣的事情发生了!在IE6里,当你点击我的第一个示例链接,呵呵,视乎是即冒泡完了又捕获了,在IE8中则只冒泡了,不过这个只是你点这个链接的时候发生的情况。接着我又算是试探性地“无意中”测试了下,点击其他的应用程序,又一个让我意想不到的情况发生了,居然先触发了window.onfocus,接着触发了lnk.onfocus!!于是我立刻测试了IE6,一样!视乎IE也“疯狂”了一把,触发了onfocus的捕获哦,哈哈!!!难道IE也支持捕获,IE也疯狂???!!!!还是这个现象有其他的解释??疑惑!?!?!!呵呵,起初我确实这么想,让我惊喜了一把,不过仔细想了想,只是事件执行顺序的原因造成了这样的假象。
呵呵,原来在IE6里,点击链接,先触发了onfocus,弹出提示‘A',然后关闭弹出的提示框窗口时,窗口又获得了焦点,又触发了window的焦点事件。然后是触发了A标签的的click事件,然后关闭弹出的提示窗口时,又让窗口获得了焦点,然后又是A标签获得焦点。而IE8测试正确的,当触发了click事件后,再关闭提示窗口的时候,就不在触发window的focus。哎!空欢喜了一场,我还以为windows的IE支捕获呢!!不过也不是完全没有收获,如果你也像我这么整了,你要注意IE6会折腾两次的,不过只是在你点击的时候才会这样,如果用tab切换获得焦点,就只会触发A标签的focus事件了。
还有在Firefox(我测试的最新的3.5)中,可千万别window.onfocus,不然你就挂了!很抱歉用FIREFOX看我这篇帖子的人,OK了几次后,你就挂了!!呵呵!!!
IE在前面给了我“惊喜”,this也给我惊喜。当然主要是我以前没有注意到,但是YAHOO的工程师们早以发现了。this这个关键字是根据上下文来决定的,在我们的事件绑定的功能函数里,this应该是指向当前的元素节点对象,应该是一个Element对象。我想这个大家应该好理解:
示例代码:
<code><br><span id="cnt0"><br> <a href=”#1″ id=”link0″>点击我触发捕获型事件流</a><br></span>
이 코드는 예제 (1)에서 처리한 방식과 정확히 같습니다. 예제에서는 a 태그의 요소 개체를 가리키므로 a 태그의 tagName 속성 'A'를 가져올 수 있습니다. 예제 (2)에서는 호환 가능한 이벤트 수신 기능을 사용합니다.
DOM 이벤트 스트리밍을 지원하는 브라우저에서는 this.tagName이 'A'라는 올바른 프롬프트를 얻을 수도 있습니다. 문제는 IE에 있습니다. attachmentEvent를 사용하여 이벤트를 요소에 바인딩할 때 다음 링크를 클릭합니다.
예(3):
이것이 무엇인가요?
샘플 코드:
function whatIsThis(){
if (this === window) {
Alert('이것은 창 객체입니다.');
Alert('그래서 This.tagName은 다음과 같습니다. :' ''' this.tagName ''.');
}
확실히 보셨나요? IE6~8에서 테스트하는 경우에는 태그 대신 창 개체를 클릭합니다. 희미한! ! ! -_-! 그래서 주의해야 합니다. 이 키워드의 문제를 해결하려면 전통적인 'onclick'을 직접 사용하거나 이전 바인딩 이벤트 수신 기능을 수정하는 것이 좋습니다. 🎜>
수정된 코드: function addEvent(el, event, fn, obj, overrideContext, bCapture){
if (overrideContext) {
if (overrideContext === true) {
context = obj;} else { context = overrideContext;
} } WrappedFn = function(){ return fn.call(context); }; try { el.addEventListener(event, WrappedFn, isCapture); } catch (e) { try { el.attachEvent('on' event, WrappedFn); } catch (e) { el['on' event] = WrappedFn; } }} 예(4): 나를 다시 클릭하여 내 '이것'이 무엇인지 확인하시겠습니까? 그게 전부입니다. 마지막으로 이 기사의 일부 아이디어는 "Javascript Advanced 프로그래밍"(아주 좋은 책이므로 모두가 읽어 보시기 바랍니다!)을 참조했습니다. . addEvent 함수는 YUI2.7의 _addListener 메소드를 사용합니다. 또한 YUI의 멋진 분들께 감사의 말씀을 전하고 싶습니다. 정적 페이지 접속 주소 : http://img.jb51.net/online/jsevent/event.htm(저의 "놀라움"도 경험하고 싶으신 분들은 IE6를 이용해 방문하시고, 첫 번째 예시 링크입니다. Firefox를 사용하지 마세요. 그렇지 않으면 작동이 중단될 것입니다. 제가 상기시키지 않았다는 말은 하지 마세요. )