javascript事件处理机制——容易被我们遗忘的细节问题
程序员文章站
2022-07-14 13:49:08
...
转自:http://www.iteye.com/topic/299320
最近在细读mootools core源代码中,在阅读到包装Event的时候,才发现,以前没对js事件处理处理机制深入的研究.遂,再次翻开尘封已久的java犀牛书,仔细的阅读了一把.
我们在书写js程序的时候,在很大程度上,都是写客户端交互程序,所以大部分都可以事件有关.但是遗憾的是,这些事件处理程序的细节非常复杂.并且由于浏览器的不同,目前有四种不同的事件处理机制:
原始事件模型:这是最简单的一种事件处理模型,尽管功能有限,但所有的浏览器都支持它.可以把它看作0级DOM
标准事件模型:这是一种更强大更完整的事件模型.2级DOM标准对它进行了标准化.Netscape 6和Mozilla支持它
Internet Explorer事件模型:具有标准事件模型的许多特性。虽然Microsoft参与了2级DOM事件模型的创建。但IE7一下的版本都没实现2级模型的标准,坚持使用自己的专有的事件模型,IE8以上的不太清楚。这意味着,在ie下运行,要想使用高级事件处理特性的js程序必须编写特定的代码。
Netscape事件模型:这种模型现在已被标准模型取代.不过Netscape6为了向后兼容,还是继续实现了它.
接下来我们讨论这几种事件模型各自的特点以及如何实现一个通用健壮的事件处理程序在你的应用程序中
其实,我们在处理js事件问题的时候,我们就会很自然的去认为事件发生在目标元素上,那么它的事件处理代码就只会在你编写的特定处理函数上执行,例如:有这样一段代码:
在通常情况下, 我们会认为当点击parent的时候会打印出'parent'来,点击child的时候会打印出'child'来,但其实则不然,当你点击child的时候,会打印出'parent'和'child'.为什么结果和预想的不一样呢,那是因为我们对js的事件处理机制还建立在原始事件模型上.而浏览器却按照标准事件模型来处理的.那或许有人又有疑问,不是说IE不支持标准事件模型吗?为什么在IE下也会打印出'child'和parent.我们刚刚说了IE 用了自己专业的事件模型处理机制.但是却具有大部分标准事件模型的特性.所以会有这样的结果.那么我们来看看标准事件模型是怎样定义的吧!
在原始事件模型中,浏览器把事件分派给发生事件的文档元素。如果该那个对象具有适合的事件处理程序,就运行这个程序。初次之外,不用执行其他的操作。但是,在标准事件模型中,情况则要复杂很多。当事件发生在目标元素时,目标的事件处理程序会被触发。此外,目标的每个祖先节点也有机会处理那个事件。事件传播分三个阶段完成。一:在捕捉(capturing)阶段,事件从Document对象沿着文档向下传播给目标节点。在这期间,目标对象的任何祖先节点都可以捕捉的事件并处理它。二:目标节点本身,这和原始时间模型相似,最后一个阶段是起泡阶段:在这个阶段,事件将从目标元素向上传播回 Document对象的文档层次。
现在就不难理解为什么我们点击child的时候parent的点击事件处理函数也会被执行.这就是我们今天讨论的主题,被忽略的细节.虽然很多人都知道 javascript标准事件模型,但是,大部分人在写事件处理程序的时候却忽略了这些问题.所以.有些时候,在某段页面js代码中插入几段事件代码,却产生莫名的错误或者是与想要的结果有大大的出入.如果说你还没有遇到这样的问题,你应该为此而感到庆幸,那是因为你还没遇到过在一个父元素和子元素处理相同的事件.但这不代表你以后不会遇到.所以为了你代码的健壮性和易维护性.应该重构你的代码.下面这段代码是我的一个实现
重构后的事件处理代码:
经过这样处理以后,便可保证在任何情况任何环境下照常运行
最近在细读mootools core源代码中,在阅读到包装Event的时候,才发现,以前没对js事件处理处理机制深入的研究.遂,再次翻开尘封已久的java犀牛书,仔细的阅读了一把.
我们在书写js程序的时候,在很大程度上,都是写客户端交互程序,所以大部分都可以事件有关.但是遗憾的是,这些事件处理程序的细节非常复杂.并且由于浏览器的不同,目前有四种不同的事件处理机制:
原始事件模型:这是最简单的一种事件处理模型,尽管功能有限,但所有的浏览器都支持它.可以把它看作0级DOM
标准事件模型:这是一种更强大更完整的事件模型.2级DOM标准对它进行了标准化.Netscape 6和Mozilla支持它
Internet Explorer事件模型:具有标准事件模型的许多特性。虽然Microsoft参与了2级DOM事件模型的创建。但IE7一下的版本都没实现2级模型的标准,坚持使用自己的专有的事件模型,IE8以上的不太清楚。这意味着,在ie下运行,要想使用高级事件处理特性的js程序必须编写特定的代码。
Netscape事件模型:这种模型现在已被标准模型取代.不过Netscape6为了向后兼容,还是继续实现了它.
接下来我们讨论这几种事件模型各自的特点以及如何实现一个通用健壮的事件处理程序在你的应用程序中
其实,我们在处理js事件问题的时候,我们就会很自然的去认为事件发生在目标元素上,那么它的事件处理代码就只会在你编写的特定处理函数上执行,例如:有这样一段代码:
1. <div id="parent" style="height: 100px; width: 200px; background-color: yellow" onclick="parent(event)"> 2. <div id="child" style="height: 50px; width: 100px; background-color: gray" onclick="child(event)"> 3. </div> 4. </div> <div id="parent" style="height: 100px; width: 200px; background-color: yellow" onclick="parent(event)"> <div id="child" style="height: 50px; width: 100px; background-color: gray" onclick="child(event)"> </div> </div>
1. var parent = function(e) { 2. alert('parent'); 3. }; 4. var child = function(e) { 5. alert('child'); 6. }; var parent = function(e) { alert('parent'); }; var child = function(e) { alert('child'); };
在通常情况下, 我们会认为当点击parent的时候会打印出'parent'来,点击child的时候会打印出'child'来,但其实则不然,当你点击child的时候,会打印出'parent'和'child'.为什么结果和预想的不一样呢,那是因为我们对js的事件处理机制还建立在原始事件模型上.而浏览器却按照标准事件模型来处理的.那或许有人又有疑问,不是说IE不支持标准事件模型吗?为什么在IE下也会打印出'child'和parent.我们刚刚说了IE 用了自己专业的事件模型处理机制.但是却具有大部分标准事件模型的特性.所以会有这样的结果.那么我们来看看标准事件模型是怎样定义的吧!
在原始事件模型中,浏览器把事件分派给发生事件的文档元素。如果该那个对象具有适合的事件处理程序,就运行这个程序。初次之外,不用执行其他的操作。但是,在标准事件模型中,情况则要复杂很多。当事件发生在目标元素时,目标的事件处理程序会被触发。此外,目标的每个祖先节点也有机会处理那个事件。事件传播分三个阶段完成。一:在捕捉(capturing)阶段,事件从Document对象沿着文档向下传播给目标节点。在这期间,目标对象的任何祖先节点都可以捕捉的事件并处理它。二:目标节点本身,这和原始时间模型相似,最后一个阶段是起泡阶段:在这个阶段,事件将从目标元素向上传播回 Document对象的文档层次。
现在就不难理解为什么我们点击child的时候parent的点击事件处理函数也会被执行.这就是我们今天讨论的主题,被忽略的细节.虽然很多人都知道 javascript标准事件模型,但是,大部分人在写事件处理程序的时候却忽略了这些问题.所以.有些时候,在某段页面js代码中插入几段事件代码,却产生莫名的错误或者是与想要的结果有大大的出入.如果说你还没有遇到这样的问题,你应该为此而感到庆幸,那是因为你还没遇到过在一个父元素和子元素处理相同的事件.但这不代表你以后不会遇到.所以为了你代码的健壮性和易维护性.应该重构你的代码.下面这段代码是我的一个实现
1. var Event = function(event) { 2. this.event = event || window.event; 3. this.target = (function() { 4. var target = this.event.target || this.event.srcElement; 5. while (target && target.nodeType == 3) { 6. target = target.parentNode; 7. } 8. return target; 9. })(); 10. 11. this.stop = function(){ 12. return this.stopPropagation().preventDefault(); 13. } 14. 15. this.stopPropagation = function(){ 16. if (this.event.stopPropagation) this.event.stopPropagation(); 17. else this.event.cancelBubble = true; 18. return this; 19. } 20. 21. this.preventDefault = function(){ 22. if (this.event.preventDefault) this.event.preventDefault(); 23. else this.event.returnValue = false; 24. return this; 25. } 26. } var Event = function(event) { this.event = event || window.event; this.target = (function() { var target = this.event.target || this.event.srcElement; while (target && target.nodeType == 3) { target = target.parentNode; } return target; })(); this.stop = function(){ return this.stopPropagation().preventDefault(); } this.stopPropagation = function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; } this.preventDefault = function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }
重构后的事件处理代码:
1. var parent = function(e) { 2. var event = new Event(e); 3. alert('parent'); 4. }; 5. var child = function(e) { 6. var event = new Event(e); 7. alert('child'); 8. event.stopPropagation(); 9. }; var parent = function(e) { var event = new Event(e); alert('parent'); }; var child = function(e) { var event = new Event(e); alert('child'); event.stopPropagation(); };
经过这样处理以后,便可保证在任何情况任何环境下照常运行