JavaScript大杂烩11 - 理解事件驱动
2014-06-23 12:38
127 查看
前面我们回顾了前端JavaScript只干的两件事:操作BOM与操作DOM,那么什么时候去干这些事呢?答案是需要干的时候去干。那么什么时候是需要干的时候呢?答案是事件被触发的时候。这就是通常所说的“事件驱动开发”。在事件被触发之前,所有的结构被静态的呈现出来,在事件触发滞后,动态的行为发生,重新产生新的静态结构,事件与状态构成了事件驱动开发的基本要素。下面我们来看看JavaScript中事件的方方面面。
事件的类型
事件指由用户直接触发的行为,通常包括下列各种事件:
鼠标事件: 鼠标单击(click, mousedown, mousemove),双击(dbclick),滚轮事件(mousewheel),鼠标移动(mousemove, mouseover, mouseout)。
键盘事件: 键按下,松开 (keydown, keypress, keyup)。
除了上面直接出发的事件外,还有一些事件是由上面的行为引发的事件: 获得/失去焦点(focus, blur),打开/关闭页面(load, unload),选中文本(select),修改文本内容(change),窗口调整(resize)等。
事件的响应 - 回调函数
用户出发了事件,那么程序就要做出适当的响应,这个是通过调用回调函数实现的。回调函数,顾名思义,就是设置好这个函数,当事件触发的时候,再回过头来调用这个设置好的函数。设置回调函数非常简单,通常来说,有如下的一些做法:
1. 静态绑定
这种做法是在书写HTML的时候就绑定好其回调函数,看个例子:
当然了也有人直接在onclick属性中写上语句,严重不可取。
这类做法不太好的地方是不太符合Web开发中UI与逻辑相分离的原则,因为HTML元素与事件中JavaScript的函数交织在一起了,扩展性不是很好。
2. 动态绑定
这种做法是完全在JavaScript代码中绑定回调函数,HTML部分完全不知道任何逻辑的代码,动态绑定又有两种做法。
第一种是直接给HTML对象的相关回调函数赋值,看个例子:
这里的事件回调函数有个规律,就是回调函数的名字都是事件名前面加了前缀"on"。
第二种是使用相关的注册时间的函数来完成挂接事件的过程,看个例子:
注意上面的代码中attachEvent的第一个参数需要加上个前缀"on",addEventListener第三个参数为false表示事件冒泡时挂接,true表示事件捕获时挂接,attachEvent没有第三个参数,默认就是冒泡时挂接,捕获和冒泡的概念下面会讲到。
动态绑定能很好的隔离逻辑与UI的代码,是大家的常用做法,而动态绑定的第一种做法方便快捷,但是不能绑定多个回调函数,而第二种做法可以绑定多个回调函数。但是第二种做法也有一个问题,就是IE浏览器不是使用标准的addEventListener去绑定事件,而是使用了attachEvent方法,实在无语,为了解决这个兼容性问题,一般我们需要写一个的辅助类去处理这种不同,例如网友常用的一个是:
兼容性问题还不止这么多,还有添加多个事件的时候,执行的结果也不大一致,而且对于document的click事件,不同的浏览器反应也是不同,这么的不一致都需要在这个辅助类里面去处理,十分的不方便。所以推荐的方法是使用现有的类库去挂接函数,比如JQuery,这些类库会很好的封装这种差异性,让你完全不去考虑兼容性问题。
事件机制
我个人没有去深入的探究浏览器里面的事件到底是怎么触发的爱好,所以对事件的机制理解的也就停留在捕获和冒泡这个水平。
当用户做了某些操作,触发一个事件的时候,事件的处理会先后经过下列的阶段:
用网上著名的一副图来说明,就是这样:
这幅图生动的说明了JavaScript事件的传递过程,从document对象到点击的text对象都可以去处理这个事件。以网上某位仁兄的例子来说明一下这个过程:
鼠标点击一下child元素,观察一下这4个事件的响应顺序来验证一下上面事件传递的3个过程吧。
这里回过来来再看一下动态绑定的两种方式,直接给onclick这种成员赋值的方式其实是在冒泡阶段挂接事件,而使用addEventListener时,第三个参数为false表示事件冒泡时挂接,true表示事件捕获时挂接,灵活性更高,这个前面也先提到过了。虽然很灵活,大家还是都习惯在冒泡的时候去挂接事件,因为毕竟很多方式还是不支持捕获期间挂接事件的。
回调函数的参数问题 - 事件对象
回调函数可以传入自己的参数,但是有一个参数是特殊的,那就是event对象,当浏览器触发一个事件以后,有的浏览器会将这个event对象添加到window对象上去,这样需要使用这个对象获取一些信息的时候就很方便,因为全局都可以直接访问window对象。有的浏览器却构造的是临时对象,只在回调函数的作用域内起作用,这些行为使得我们不得不将其作为参数传进去:
这个例子的代码改造自上面的例子,下面的所有小例子也是一样。
通过上面的做法,这样我们就获取到了事件对象,这个对象有很多的方法,下面就看看几种常见的应用场景:
1. 事件的截断
事件的捕获与冒泡过程对大多数的逻辑来说并没什么问题,因为大部分情况下只有一个相关的元素会去挂接这个事件。但是有时候,比如UI有些重叠的时候,或者是说外层元素和内层元素都挂接了同一个事件的时候,我们通常希望有一个元素响应以后,事件就不再传递了,到此结束。要达到这种效果,可以这么修改上面的例子:
这样当p元素捕获这个事件并处理后,事件就不再继续传递了。
毫无疑问,出现两种方式的原因还是浏览器的兼容性问题,比如我测试了一下两种方式,方式1在Chrome中能工作,在IE10中却不行,而方式2在两个浏览器中都能工作。所以更通用的写法是自己搞个辅助类:
这样使用起来就像这样:
注意这个辅助类Event是工作在回调函数里面,而上面的那个辅助类Events是工作在回调函数外面,帮助挂接回调函数的,不要混淆。
2. 阻止浏览器默认的响应
对于很多的事件来说,浏览器可能会提供默认的一些响应,而如果我们要响应自己的行为,不让浏览器去处理,则需要阻止浏览器的默认行为。要达到这种效果,可以这么做:
不用说,又是浏览器的兼容性问题,这个方法我们也加到了上面的那个辅助类中。使用辅助类可以这么写:
不过需要注意,阻止浏览器默认的响应并不会阻止事件继续传递。
3. 获取事件目标对象
获取事件的目标对象几乎是事件对象最重要的用途了,这个可以使用srcElement或者target获取到,当然了,结合前面总结的this的用法,我们也可以通过某些this来得到事件的目标对象,这种方式这里不再多讲了。看例子:
4. 获取用户的输入信息
1). 获取鼠标按键
获取鼠标按键也是一个很常用的操作,比如实现右键菜单,这个是通过button得到的。
button代表被按下的鼠标键,值是整数,1代表左键,2代表右键,4代表中键,如果按下多个键,结果会把这些值加起来,所以3就代表左右键同时按下(注意firefox中,0代表左键,1代表中间键,2代表右键)。
2). 获取键盘按键
按键信息可以通过keyCode或者which获取到,另外是否按下特殊键可以通过altKey,ctrlKey,shiftKey这3个bool值知晓。
3). 获取坐标信息
此外很多时候,获取目标对象的一些窗口数据也很重要,这个可以通过下列成员得到:
针对上面的成员,看个简单的例子:
这个例子中的c元素就是上面的嵌套的那个例子中的元素,在不同的浏览器上检查一下输出的数据,体会一下兼容性问题,然后试着把这些属性都加到上面的Event辅助类中去吧。
事件的类型
事件指由用户直接触发的行为,通常包括下列各种事件:
鼠标事件: 鼠标单击(click, mousedown, mousemove),双击(dbclick),滚轮事件(mousewheel),鼠标移动(mousemove, mouseover, mouseout)。
键盘事件: 键按下,松开 (keydown, keypress, keyup)。
除了上面直接出发的事件外,还有一些事件是由上面的行为引发的事件: 获得/失去焦点(focus, blur),打开/关闭页面(load, unload),选中文本(select),修改文本内容(change),窗口调整(resize)等。
事件的响应 - 回调函数
用户出发了事件,那么程序就要做出适当的响应,这个是通过调用回调函数实现的。回调函数,顾名思义,就是设置好这个函数,当事件触发的时候,再回过头来调用这个设置好的函数。设置回调函数非常简单,通常来说,有如下的一些做法:
1. 静态绑定
这种做法是在书写HTML的时候就绑定好其回调函数,看个例子:
<input type="button" value="Click Me" onclick="demo()"></input> <script> function demo() { alert('click event'); } </script>
当然了也有人直接在onclick属性中写上语句,严重不可取。
这类做法不太好的地方是不太符合Web开发中UI与逻辑相分离的原则,因为HTML元素与事件中JavaScript的函数交织在一起了,扩展性不是很好。
2. 动态绑定
这种做法是完全在JavaScript代码中绑定回调函数,HTML部分完全不知道任何逻辑的代码,动态绑定又有两种做法。
第一种是直接给HTML对象的相关回调函数赋值,看个例子:
<input id="btnStart" type="button" value="Click Me"></input> <script> var btnStart = document.getElementById('btnStart'); btnStart.onclick = function() { alert('click event'); }; </script>
这里的事件回调函数有个规律,就是回调函数的名字都是事件名前面加了前缀"on"。
第二种是使用相关的注册时间的函数来完成挂接事件的过程,看个例子:
var btnStart = document.getElementById('btnStart'); var demo = function() { alert('click event'); }; // 非IE浏览器 if(btnStart.addEventListener){ btnStart.addEventListener('click', demo, false); } // IE浏览器 if(btnStart.attachEvent){ btnStart.attachEvent('onclick', demo); }
注意上面的代码中attachEvent的第一个参数需要加上个前缀"on",addEventListener第三个参数为false表示事件冒泡时挂接,true表示事件捕获时挂接,attachEvent没有第三个参数,默认就是冒泡时挂接,捕获和冒泡的概念下面会讲到。
动态绑定能很好的隔离逻辑与UI的代码,是大家的常用做法,而动态绑定的第一种做法方便快捷,但是不能绑定多个回调函数,而第二种做法可以绑定多个回调函数。但是第二种做法也有一个问题,就是IE浏览器不是使用标准的addEventListener去绑定事件,而是使用了attachEvent方法,实在无语,为了解决这个兼容性问题,一般我们需要写一个的辅助类去处理这种不同,例如网友常用的一个是:
var Events = { //添加事件 add : function(element, type, callback){ if(element.addEventListener){ element.addEventListener(type, callback, false); }else{ // 解决document点击的事件处理不一致问题 element['e'+callback] = function(){ callback.call(element,window.event); }; element.attachEvent('on' + type, element['e'+callback]); } }, //删除事件 remove : function(element, type, callback){ if(element.removeEventListener){ element.removeEventListener(type, callback, false); }else if(element.detachEvent){ element.detachEvent('on' + type, element['e'+callback]); } }, //主动的通过程序去触发事件 dispatch : function(element ,type){ try{ if(element.dispatchEvent){ var evt = document.createEvent('Event'); evt.initEvent(type,true,true); element.dispatchEvent(evt); }else if(element.fireEvent){ element.fireEvent('on'+type); } }catch(e){}; } }; var btnStart = document.getElementById('btnStart'); var demo = function() { alert('click event'); }; Events.add(btnStart, 'click', demo);
兼容性问题还不止这么多,还有添加多个事件的时候,执行的结果也不大一致,而且对于document的click事件,不同的浏览器反应也是不同,这么的不一致都需要在这个辅助类里面去处理,十分的不方便。所以推荐的方法是使用现有的类库去挂接函数,比如JQuery,这些类库会很好的封装这种差异性,让你完全不去考虑兼容性问题。
事件机制
我个人没有去深入的探究浏览器里面的事件到底是怎么触发的爱好,所以对事件的机制理解的也就停留在捕获和冒泡这个水平。
当用户做了某些操作,触发一个事件的时候,事件的处理会先后经过下列的阶段:
1. 事件捕获阶段:从可以接收该事件的最外层元素开始询问是否处理该事件,处理完后,该事件则继续一层一层向内层目标元素传递,到达最内层的目标元素时事件捕获阶段结束。 2. 目标元素处理阶段: 事件捕获阶段结束后,事件传递到了最内层的目标元素,这个时候目标元素处理该事件,事件处理完后,该事件进入冒泡阶段。 3. 事件冒泡阶段:进入冒泡阶段后,事件一层一层向上传递,每一层元素都可以处理该事件,处理完后,事件继续向外层元素传递直到最外层元素,然后结束。
用网上著名的一副图来说明,就是这样:
这幅图生动的说明了JavaScript事件的传递过程,从document对象到点击的text对象都可以去处理这个事件。以网上某位仁兄的例子来说明一下这个过程:
<html> <head> <title></title> <style type="text/css"> #p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; } #c { width: 100px; height: 100px; border: 1px solid red; } </style> </head> <body> <div id="p"> parent <div id="c"> child </div> </div> <script type="text/javascript"> var p = document.getElementById('p'), c = document.getElementById('c'); c.addEventListener('click', function () { alert('子节点捕获'); }, true); c.addEventListener('click', function () { alert('子节点冒泡'); }, false); p.addEventListener('click', function () { alert('父节点捕获'); }, true); p.addEventListener('click', function () { alert('父节点冒泡'); }, false); </script> </body> </html>
鼠标点击一下child元素,观察一下这4个事件的响应顺序来验证一下上面事件传递的3个过程吧。
这里回过来来再看一下动态绑定的两种方式,直接给onclick这种成员赋值的方式其实是在冒泡阶段挂接事件,而使用addEventListener时,第三个参数为false表示事件冒泡时挂接,true表示事件捕获时挂接,灵活性更高,这个前面也先提到过了。虽然很灵活,大家还是都习惯在冒泡的时候去挂接事件,因为毕竟很多方式还是不支持捕获期间挂接事件的。
回调函数的参数问题 - 事件对象
回调函数可以传入自己的参数,但是有一个参数是特殊的,那就是event对象,当浏览器触发一个事件以后,有的浏览器会将这个event对象添加到window对象上去,这样需要使用这个对象获取一些信息的时候就很方便,因为全局都可以直接访问window对象。有的浏览器却构造的是临时对象,只在回调函数的作用域内起作用,这些行为使得我们不得不将其作为参数传进去:
p.addEventListener('click', function (evt) { var evt = evt || window.event; alert(evt); alert('父节点捕获'); }, true);
这个例子的代码改造自上面的例子,下面的所有小例子也是一样。
通过上面的做法,这样我们就获取到了事件对象,这个对象有很多的方法,下面就看看几种常见的应用场景:
1. 事件的截断
事件的捕获与冒泡过程对大多数的逻辑来说并没什么问题,因为大部分情况下只有一个相关的元素会去挂接这个事件。但是有时候,比如UI有些重叠的时候,或者是说外层元素和内层元素都挂接了同一个事件的时候,我们通常希望有一个元素响应以后,事件就不再传递了,到此结束。要达到这种效果,可以这么修改上面的例子:
c.addEventListener('click', function (evt) { var evt = evt || window.event; // 方式1: 使用stopPropagation方法 // evt.stopPropagation(); // 方式2: 使用cancelBubble属性 evt.cancelBubble = true; }, false);
这样当p元素捕获这个事件并处理后,事件就不再继续传递了。
毫无疑问,出现两种方式的原因还是浏览器的兼容性问题,比如我测试了一下两种方式,方式1在Chrome中能工作,在IE10中却不行,而方式2在两个浏览器中都能工作。所以更通用的写法是自己搞个辅助类:
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; }; };
这样使用起来就像这样:
c.addEventListener('click', function (evt) { var event = new Event(e); event.stopPropagation(); }, false);
注意这个辅助类Event是工作在回调函数里面,而上面的那个辅助类Events是工作在回调函数外面,帮助挂接回调函数的,不要混淆。
2. 阻止浏览器默认的响应
对于很多的事件来说,浏览器可能会提供默认的一些响应,而如果我们要响应自己的行为,不让浏览器去处理,则需要阻止浏览器的默认行为。要达到这种效果,可以这么做:
c.addEventListener('click', function (evt) { var evt = evt || window.event; // 方式1: 使用preventDefault方法 // evt.preventDefault(); // 方式2: 使用returnValue 属性 evt.returnValue = false; }, false);
不用说,又是浏览器的兼容性问题,这个方法我们也加到了上面的那个辅助类中。使用辅助类可以这么写:
c.addEventListener('click', function (evt) { var event = new Event(e); event.preventDefault(); }, false);
不过需要注意,阻止浏览器默认的响应并不会阻止事件继续传递。
3. 获取事件目标对象
获取事件的目标对象几乎是事件对象最重要的用途了,这个可以使用srcElement或者target获取到,当然了,结合前面总结的this的用法,我们也可以通过某些this来得到事件的目标对象,这种方式这里不再多讲了。看例子:
var c = document.getElementById('c'); c.addEventListener('click', function (evt) { var evt = evt || window.event; alert(evt.target); alert(evt.srcElement); alert(this); }, false);
4. 获取用户的输入信息
1). 获取鼠标按键
获取鼠标按键也是一个很常用的操作,比如实现右键菜单,这个是通过button得到的。
button代表被按下的鼠标键,值是整数,1代表左键,2代表右键,4代表中键,如果按下多个键,结果会把这些值加起来,所以3就代表左右键同时按下(注意firefox中,0代表左键,1代表中间键,2代表右键)。
2). 获取键盘按键
按键信息可以通过keyCode或者which获取到,另外是否按下特殊键可以通过altKey,ctrlKey,shiftKey这3个bool值知晓。
3). 获取坐标信息
此外很多时候,获取目标对象的一些窗口数据也很重要,这个可以通过下列成员得到:
clientX/clientY:事件发生的时候,鼠标相对于浏览器窗口可视文档区域的左上角的位置;(在DOM标准中,这两个属性值都不考虑文档的滚动情况,也就是说,无论文档滚动到哪里,只要事件发生在窗口左上角,clientX和clientY都是0,所以在IE中,要想得到事件发生的坐标相对于文档开头的位置,要加上 document.body.scrollLeft和 document.body.scrollTop)。 offsetX,offsetY/layerX,layerY:事件发生的时候,鼠标相对于源元素左上角的位置。 x,y/pageX,pageY:检索相对于父元素来说,鼠标水平/垂直坐标的值。 screenX、screenY:鼠标指针相对于显示器左上角的位置。
针对上面的成员,看个简单的例子:
var c = document.getElementById('c'); c.addEventListener('click', function (evt) { var evt = evt || window.event; document.write('button: ' + evt.button + '<br/>'); document.write('keyCode: ' + evt.keyCode + '<br/>'); document.write('which: ' + evt.which + '<br/>'); document.write('altKey: ' + evt.altKey + '<br/>'); document.write('ctrlKey: ' + evt.ctrlKey + '<br/>'); document.write('shiftKey: ' + evt.shiftKey + '<br/>'); document.write('clientX: ' + evt.clientX + '<br/>'); document.write('clientY: ' + evt.clientY + '<br/>'); document.write('offsetX: ' + evt.offsetX + '<br/>'); document.write('offsetY: ' + evt.offsetY + '<br/>'); document.write('layerX: ' + evt.layerX + '<br/>'); document.write('layerY: ' + evt.layerY + '<br/>'); document.write('x: ' + evt.x + '<br/>'); document.write('y: ' + evt.y + '<br/>'); document.write('pageX: ' + evt.pageX + '<br/>'); document.write('pageY: ' + evt.pageY + '<br/>'); document.write('screenX: ' + evt.screenX + '<br/>'); document.write('screenY: ' + evt.screenY + '<br/>'); }, false);
这个例子中的c元素就是上面的嵌套的那个例子中的元素,在不同的浏览器上检查一下输出的数据,体会一下兼容性问题,然后试着把这些属性都加到上面的Event辅助类中去吧。
相关文章推荐
- 深入理解javaScript中的事件驱动
- 深入理解javaScript中的事件驱动
- 韩顺平 javascript教学视频_学习笔记23_js事件驱动机制深入理解_js常用事件_js版计算器
- JavaScript事件驱动及事件处理
- 理解 Delphi 的类(十一) - 深入类中的方法[11] - 事件方法
- javascript中事件的理解
- 理解JavaScript中的事件(收藏)
- 理解JavaScript中的事件
- 理解JavaScript中的事件
- JavaScript事件的理解
- 事件驱动的Web之旅——JSP与JavaScript的融合(续)
- javascript事件驱动框架
- 理解JavaScript中的事件
- 我对事件驱动的理解
- 理解JavaScript中的事件【转载】
- 理解JavaScript中的事件
- javaScript--事件驱动
- 理解JavaScript中的事件
- javascript 事件驱动杂谈