`
marb
  • 浏览: 419938 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JavaScript事件机制详细研究

 
阅读更多

本篇开始将回顾下Javascript的事件机制。同时会从一个最小的函数开始写到最后一个具有完整功能的,强大的事件模块。为叙述方便将响应函数/回调函数/事件Listener/事件handler都称为事件handler。

先看看页面中添加事件的几种方式:

    1. 直接将JS代码写在HTML上
测试:Nowamagic
1 <div onclick="alert('欢迎访问Nowamagic.net');">Nowamagic</div>

HTML Element元素自身就拥有了很多onXXX属性,只需将JS代码赋值给其就可以了。赋值给onXXX的字符串将作为响应函数的函数体 (FunctionBody)。大概这是上世纪90年代的写法,那时候直接把JS代码写在网页中很普遍,也许那时候的JS并不太重要,只是用来做做验证或 一些花哨的效果而已。

    1. 定义一个函数,赋值给html元素的onXXX属性
1 <script type="text/javascript">
2     function clk(){}
3 </script>
4 <div onclick="clk()">Div2 Element</div>

先定义函数clk,然后赋值给onclick属性,这种方式也应该属于上世纪90年代的流行写法。比第一种方式好的是它把业务逻辑代码都封装在一个函数里了,使HTML代码与JS代码稍微有点儿分离,不至于第一种那么紧密耦合。

    1. 使用element.onXXX方式
1 <div id="d3">Div3 Element</div>
2 <script type="text/javascript">  
3     var d3 = document.getElementById('d3');
4     d3.onclick = function(){    }
5 </script>

这种方式也是早期的写法,但好处是可以将JS与HTML完全分离,前提是需要给HTML元素提供一个额外的id属性(或其它能获取该元素对象的方式)。

    1. 使用addEventListener或attachEvent
01 <div id="d4">Div4 Element</div>
02 <script type="text/javascript">
03 var d4 = document.getElementById('d4');
04 function clk(){alert(4)}
05 if(d4.addEventListener){
06 d4.addEventListener('click',clk,false);
07 }
08 if(d4.attachEvent){
09 d4.attachEvent('onclick',clk);
10 }
11 </script>

这是目前推荐的方式,较前两种方式功能更为强大,可以为元素添加多个事件handler,支持事件冒泡或捕获,前三种方式默认都是冒泡。IE6/7/8仍然没有遵循标准而使用了自己专有的attachEvent,且不支持事件捕获。

好,把方式4简单的封装下, 兼容标准浏览器及IE浏览器。注意attachEvent的第一个参数需要加上个"on",addEventListener第三个参数为false表示事件冒泡,attachEvent没有第三个参数,默认就是冒泡,没有捕获。

01 /**
02  *
03  * @param {Object} el HTML Element
04  * @param {Object} type 事件类型
05  * @param {Object} fn 事件handler
06  */
07 function addEvent(el, type, fn){
08     if(el.addEventListener){
09         el.addEventListener(type, fn, false);
10     }else{
11         el.attachEvent('on' + type, fn);
12     }
13 }

好,用这个工具函数添加一个给document添加一个点击事件:

1 function handler(){
2     alert(this);
3     alert(arguments[0]);
4 }
5 addEvent(document, 'click', handler);

在Firefox等标准浏览器中,点击页面后将弹出 "[object HTMLDocument]",及handler中的this就是document自身。但在IE6/7/8中this却是window对象。这让人不爽,修改下与标准浏览器统一。

01 function addEvent(el, type, fn){
02     if(el.addEventListener){
03         el.addEventListener(type, fn, false);
04     }else
05         el['e' + fn] = function(){
06             fn.call(el, window.event);
07         }
08         el.attachEvent('on'+type, el['e'+fn]);
09     }
10 }

上面我们封装了一个addEvent,解决了IE6/7/8下事件handler中this为window的错误,并且统一了事件对象作为事件handler的第一个参数传入。

这篇把对应的删除事件的函数补上。上一篇中fn在IE6/7/8中实际上被包装了,IE6/7/8中真正的handler是el["e"+fn]。因此删除时要用到它。同时将两个方法挂在一个对象E上,add,remove分别添加和删除事件。

01 E = {
02     //添加事件
03     add : function(el, type, fn){
04         if(el.addEventListener){
05             el.addEventListener(type, fn, false);
06         }else{
07             el['e'+fn] = function(){
08                 fn.call(el,evt);
09             }; 
10             el.attachEvent('on' + type, el['e'+fn]);
11         }
12     },
13     //删除事件
14     remove : function(el, type, fn){
15         if(el.removeEventListener){
16             el.removeEventListener(type, fn, false);
17         }else if(el.detachEvent){
18             el.detachEvent('on' + type, el['e'+fn]);
19         }
20     }  
21 };

可以看到,标准浏览器如IE9/Firefox/Safari/Chrome/Opera会使用addEventListener /removeEventListener添加/删除事件,IE6/7/8则使用attachEvent/detachEvent。标准浏览器中事件 handler是传入的第三个参数fn,IE6/7/8中则是包装后的el["e"+fn]。

好了,已经拥有了添加,删除事件两个方法,并且解决了各浏览器下中的部分差异,现再添加一个主动触发事件的方法dispatch。该方法能模 拟用户行为,如点击(click)操作等。 标准使用dispatchEvent方法,IE6/7/8则使用fireEvent方法。因为可能会出现异常,使用了try catch。

01 E = {
02     //添加事件
03     add : function(el, type, fn){
04         if(el.addEventListener){
05             el.addEventListener(type, fn, false);
06         }else{
07             el['e'+fn] = function(){
08                 fn.call(el,window.event);
09             }; 
10             el.attachEvent('on' + type, el['e'+fn]);
11         }
12     },
13     //删除事件
14     remove : function(el, type, fn){
15         if(el.removeEventListener){
16             el.removeEventListener(type, fn, false);
17         }else if(el.detachEvent){
18             el.detachEvent('on' + type, el['e'+fn]);
19         }
20     },
21     //主动触发事件
22     dispatch : function(el ,type){
23         try{
24             if(el.dispatchEvent){
25                 var evt = document.createEvent('Event');
26                 evt.initEvent(type,true,true);
27                 el.dispatchEvent(evt);
28             }else if(el.fireEvent){
29                 el.fireEvent('on'+type);
30             }
31         }catch(e){};
32     }
33 };

这就是整个事件模块的雏形,往后还有很多需要补充完善的地方。但对于普通的应用,这几个函数足以胜任。

上面的add有个问题,对同一类型事件添加多个hanlder时,IE6/7/8下会无序,如

01 <div id="d1" style="width:200px;height:200px;background:gold;"></div>
02 <script type="text/javascript">
03     var el = document.getElementById('d1');
04     function handler1(){alert('1');}
05     function handler2(){alert('2');}
06     function handler3(){alert('3');}
07     function handler4(){alert('4');}
08     function handler5(){alert('5');}
09     E.add(el, 'click', handler1);
10     E.add(el, 'click', handler2);
11     E.add(el, 'click', handler3);
12     E.add(el, 'click', handler4);
13     E.add(el, 'click', handler5);
14 </script>

IE9/Firefox/Safari/Chomre/Opera会依次输出1,2,3,4,5。但IE6/7/8中则不一定。为解决所有浏览器中多个事件handler有序执行,我们需要一个队列来管理所有的handler。

这次,把所有的内部细节封装在一个匿名函数中,该函数执行完毕后返回如上一篇接口相同的方法。另外

  1. 把真正的事件handler挂在el上,即el.listeners,其为一个对象,每一个类型的事件为一个数组,如click为el.listeners["click"] = []。
  2. 所有的handler存在在对于的数组中
  3. 删除一个hanlder,将从数组中将其删除
01 E = function(){
02     function _isEmptyObj(obj){
03         for(var a in obj){
04             return false;
05         }
06         return true;
07     }
08     function _each(ary, callback){
09         for(var i=0,len=ary.length; i<len;){
10             callback(i, ary[i]) ? i=0 : i++;
11         }
12     }
13     function _remove(el, type){
14         var handler = el.listeners[type]['_handler_'];
15         el.removeEventListener ?
16             el.removeEventListener(type, handler, false) :
17             el.detachEvent('on'+type, handler);
18         delete el.listeners[type];
19         if(_isEmptyObj(el.listeners)){
20             delete el.listeners;
21         }
22     }
23     // 添加事件
24     function add(el, type, fn){
25         el.listeners = el.listeners || {};
26         var listeners = el.listeners[type] = el.listeners[type] || [];
27         listeners.push(fn);
28         if(!listeners['_handler_']){
29             listeners['_handler_'] = function(e){
30                 var evt = e || window.event;
31                 for(var i=0,fn; fn=listeners[i++];){
32                     fn.call(el, evt);
33                 }
34             }
35             el.addEventListener ?
36                 el.addEventListener(type, listeners['_handler_'], false) :
37                 el.attachEvent('on' + type,  listeners['_handler_']);
38         }
39     }
40     // 删除事件
41     function remove(el, type, fn){
42         if(!el.listeners) return;
43         var listeners = el.listeners && el.listeners[type];
44         if(listeners) {
45             _each(listeners, function(i, f){
46                 if(f==fn){
47                     return listeners.splice(i, 1);
48                 }
49             });
50             if(listeners.length == 0){
51                 _remove(el,type);
52             }
53         }
54     }
55     //主动触发事件
56     function dispatch(el ,type){
57         try{
58             if(el.dispatchEvent){
59                 var evt = document.createEvent('Event');
60                 evt.initEvent(type,true,true);
61                 el.dispatchEvent(evt);
62             }else if(el.fireEvent){
63                 el.fireEvent('on'+type);
64             }
65         }catch(e){};
66     }  
67     return {
68         add: add,
69         remove: remove,
70         dispatch: dispatch
71     };
72 }();

上面解决了IE6/7/8中同一个类型事件的多个handler执行无序的情况,为此改动也是较大的。实现几乎与前一个版本完全不同。但好处也是明显的。

有时需要添加只执行一次的事件handler,为此给add方法添加第四个参数one,one为true则该事件handler只执行一次。

1 <div id="d1" style="width:200px;height:200px;background:gold;"></div>
2 <script>
3 var el = document.getElementById('d1');
4 function handler(){alert(5)}
5 E.add(el, 'click', handler, true);
6 </script>

再扩展下remove函数。

  1. 删除元素type类型的所有监听器(参数传el,type)
  2. 删除元素所有的监听器(仅传el)

比如当给一个el添加了3个click事件的handler,1个mouseover事件的handler

1 function handler1(){alert('1');}
2 function handler2(){alert('2');}
3 function handler3(){alert('3');}
4 function handler4(){alert('4');}
5 E.add(el, 'click', f1);
6 E.add(el, 'click', f2);
7 E.add(el, 'click', f3);
8 E.add(el, 'mouseover', f4);

使用以下语句将删除元素click的所有handler:E.remove(el, 'click');

以下将删除元素身上所有的事件handler,包括click和mouseover:E.remove(el);

上面正式推出了我的事件模块event_v1,已经搭起了它的初始框架。或许有人要说,与众多JS库或框架相比,它还没有解决事件对象的兼容性问题。是的,我故意将此放到后续补充。因为事件对象的兼容性问题太多了,太繁琐了。

下面我将引入一个私有的_fixEvent函数,add中将调用该函数。_fixEvent将修复(或称包装)原生事件对象,返回一个标准的统一接口的事件对象。如下

01 function _fixEvent( evt, el ) {
02     var props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
03         len   = props.length;
04     function now() {return (new Date).getTime();}
05     function returnFalse() {return false;}
06     function returnTrue() {return true;}
07     function Event( src ) {
08         this.originalEvent = src;
09         this.type = src.type;
10         this.timeStamp = now();
11     }
12     Event.prototype = {
13         preventDefault: function() {
14             this.isDefaultPrevented = returnTrue;
15             var e = this.originalEvent;
16             if( e.preventDefault ) {
17                 e.preventDefault();
18             }
19             e.returnValue = false;
20         },
21         stopPropagation: function() {
22             this.isPropagationStopped = returnTrue;
23             var e = this.originalEvent;
24             if( e.stopPropagation ) {
25                 e.stopPropagation();
26             }      
27             e.cancelBubble = true;
28         },
29         stopImmediatePropagation: function() {
30             this.isImmediatePropagationStopped = returnTrue;
31             this.stopPropagation();
32         },
33         isDefaultPrevented: returnFalse,
34         isPropagationStopped: returnFalse,
35         isImmediatePropagationStopped: returnFalse
36     };
37  
38     var originalEvent = evt;
39     evt = new Event( originalEvent );
40      
41     for(var i = len, prop; i;) {
42         prop = props[ --i ];
43         evt[ prop ] = originalEvent[ prop ];
44     }
45     if(!evt.target) {
46         evt.target = evt.srcElement || document;
47     }
48     if( evt.target.nodeType === 3 ) {
49         evt.target = evt.target.parentNode;
50     }
51     if( !evt.relatedTarget && evt.fromElement ) {
52         evt.relatedTarget = evt.fromElement === evt.target ? evt.toElement : evt.fromElement;
53     }
54     if( evt.pageX == null && evt.clientX != null ) {
55         var doc = document.documentElement, body = document.body;
56         evt.pageX = evt.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
57         evt.pageY = evt.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
58     }
59     if( !evt.which && ((evt.charCode || evt.charCode === 0) ? evt.charCode : evt.keyCode) ) {
60         evt.which = evt.charCode || evt.keyCode;
61     }
62     if( !evt.metaKey && evt.ctrlKey ) {
63         evt.metaKey = evt.ctrlKey;
64     }
65     if( !evt.which && evt.button !== undefined ) {
66         evt.which = (evt.button & 1 ? 1 : ( evt.button & 2 ? 3 : ( evt.button & 4 ? 2 : 0 ) ));
67     }      
68     if(!evt.currentTarget) evt.currentTarget = el;
69  
70     return evt;
71 }  

好了,现在你要

  1. 阻止事件默认行为,统一使用e.preventDefault()
  2. 停止冒泡,统一使用e. stopPropagation()
  3. 获取事件源,统一使用e.target
  4. ……/li>

更多的差异性,不在这一一列举了。

分享到:
评论

相关推荐

    JavaScript继承机制研究.pdf

    JavaScript继承机制研究 在本文中,我们将深入探讨JavaScript继承机制的实现方式,并对基于原型的继承、构造函数方式继承、组合继承、寄生式继承等继承机制进行了总结归纳和分析。 基于原型的继承 JavaScript是...

    【移动端兼容问题研究】javascript事件机制详解(涉及移动兼容)1

    JavaScript 事件机制是网页交互的核心,它使得网页具备响应用户操作的能力。在移动端,考虑到不同设备和浏览器的差异,理解事件机制以及如何处理兼容性问题至关重要。本文将深入探讨JavaScript事件的捕获/冒泡阶段,...

    JavaScript验证机制输入的测试工程

    在这个"JavaScript验证机制输入的测试工程"中,我们可以深入研究如何利用JavaScript实现各种常见的输入验证。 1. **基础概念**: - **JavaScript**:JavaScript是一种轻量级的解释型编程语言,广泛用于网页和网络...

    网页模板——扣代码工具 javascript事件 捕获者2.0.zip

    JavaScript事件捕获机制是理解JavaScript事件处理模型的关键部分,它允许我们对页面上的元素进行响应,例如点击、鼠标移动或者键盘输入等。下面将详细阐述这两个主题。 首先,网页模板是一种预先设计的网页结构,...

    JavaScript计算性能对比研究.pdf

    JavaScript计算性能对比研究 本文对JavaScript的计算性能进行了研究,对比了JavaScript和主流编程语言Java、C++、MATLAB等语言的计算能力、数据精度以及处理时间。研究发现,在较大迭代次数条件下,JavaScript与C++...

    第三方JavaScript代码沙盒技术研究.pdf

    第三方JavaScript代码沙盒技术研究 本文研究了第三方JavaScript代码沙盒技术的应用,旨在解决Mashup风格的Web应用中集成不可信的第三方JavaScript代码的问题。通过分析当前浏览器的主要安全机制,提出了沙盒技术所...

    嵌入式JavaScript对象实现技术研究.pdf

    总结来说,这篇论文深入研究了嵌入式JavaScript对象的实现技术,包括解释系统的架构和优化算法,特别是对象处理和事件响应的实现。这些研究成果对于理解和优化嵌入式环境中的JavaScript性能具有重要意义,为嵌入式...

    基于数学思想与JavaScript的滑动事件研究与设计.pdf

    本文将探讨如何利用JavaScript结合数学中的三角函数,设计并实现一种有效的移动端滑动事件处理机制。 首先,需要了解移动端页面动作捕获及分析流程。移动端页面通过对DOM对象上的事件进行监听来捕获用户的操作,这...

    javascript作业题答案

    原型则涉及JavaScript的对象继承机制,所有对象都具有一个`__proto__`属性,可以通过原型链来共享属性和方法。 文件名“第1章”可能暗示了这是一个逐步深入的系列教程,第一章可能涵盖基础概念,如变量声明、数据...

    一个使用JavaScript写的星际争霸网页游戏!超牛..纯JavaScript,值得研究

    此外,事件监听和处理也是JavaScript实现游戏互动的关键,比如玩家点击屏幕时触发的单位移动、攻击等操作。 JavaScript的异步处理机制在游戏中的应用也不容忽视。游戏中的动画效果和实时更新需要频繁的定时任务,...

    JavaScript的简单且小巧119字节事件发射器库

    标题提到的"JavaScript的简单且小巧119字节事件发射器库"正是这种设计理念的体现,它在极小的代码体积下实现了事件驱动的机制。 事件发射器库,通常包含两个主要功能:注册事件监听器和触发事件。在JavaScript中,...

    Extjs源码之--Ext事件机制/继承关系

    在EXTJS这个强大的JavaScript框架中,事件机制是其核心组件之一,它允许组件之间进行通信和交互。在“Extjs源码之--Ext事件机制/继承关系”中,我们将深入探讨EXTJS如何处理事件以及其类层次结构。EXTJS的事件处理...

    javascript脚本病毒的编写与防范方法研究.doc

    JavaScript脚本病毒的编写与防范方法研究 JavaScript脚本病毒是指使用JavaScript语言编写的恶意程序,能够感染和破坏计算机系统的安全。JavaScript脚本病毒的编写与防范方法研究是信息安全领域中的重要课题,本文将...

    基于VRML和JavaScript的动画控制机制.pdf

    本文总结了基于VRML和JavaScript的动画控制机制的研究成果。VRML(Virtual Reality Modeling Language)是一种用于Internet和Web超链接上的多用户交互、独立于计算机平台的网络虚拟现实建模语言。当前基于VRML的虚拟...

    Head First Javascript源码

    3. **事件处理**:JavaScript通常用于增强网页的交互性,通过监听和响应用户的动作,如点击按钮、滚动页面等。 4. **DOM操作**:Document Object Model (DOM)是HTML和XML文档的结构化表示,JavaScript可以用来遍历...

    javascript键盘事件管理工具

    通过深入研究这些源码,开发者不仅可以理解如何有效地处理键盘事件,还能借鉴其设计模式和编程技巧,提升自己的JavaScript技能。此外,如果这个工具提供了良好的文档和示例,那么它将是一个极好的学习资源,帮助...

    JavaScript 5.CHM

    最后,我们不能忽视JavaScript的事件模型,包括DOM事件、事件冒泡和事件捕获,以及事件监听器的添加和移除。这对于编写交互性前端应用非常重要。 总结起来,JavaScript 5.CHM提供了全面的JavaScript 5语言参考,...

    JavaScript菜鸟学习教程

    JavaScript的事件处理机制让网页具有了用户交互性。事件是用户或浏览器执行的特定动作,如点击、滚动、提交表单等。学习如何绑定事件处理器(如addEventListener)以及编写响应事件的函数,是创建动态网页的重要技能...

    JavaScript前端开发案例教程-源代码.rar

    对象则是一种数据结构,允许我们存储键值对,通过原型链机制,JavaScript实现了面向对象编程的一些特性。在压缩包中的案例可能涵盖了这些基本概念的实践应用。 其次,前端开发不仅限于JavaScript,还涉及到HTML和...

Global site tag (gtag.js) - Google Analytics