JavaScript之事件处理详解
一.事件传播机制
客户端JavaScript程序(就是浏览器啦)采用了异步事件驱动编程模型。当文档、浏览器、元素或与之相关的对象发生某些有趣的事情时,Web浏览器就会产生事件(event)。如果JavaScript应用程序关注特定类型的事件,那么它可以注册当这类事件发生时要调用的一个或多个函数。当然了,这种风格并非Web编程独有,所有使用图形用户界面的应用程序都采用了它。
既然要详解事件处理,那我们先从几个基础概念说起吧:
①事件类型(event type):是一个用来说明发生什么类型事件的字符串。例如,“mousemove”表示用户移动鼠标,“keydown”表示键盘上某个键被按下。事件类型只是一个字符串,有时候又称之为事件名字(event name);
②事件目标(event target):是发生事件或与之相关的对象。Window、Document和Element对象是最常见的事件目标。当然,AJAX中的XMLHttpRequest对象也是一个事件目标;
③事件处理程序(event handler):是处理或响应事件的函数,它也叫事件监听程序(event listener)。应用程序通过指明事件类型和事件目标,在Web浏览器中注册它们的事件处理函数。
④事件对象(event object):是与特定事件相关且包含有关该事件详细信息的对象。事件对象作为参数传递给事件处理函数(但是在IE8以及其之前版本中,全局变量event才是事件对象)。事件对象都有用来指定事件类型(event type)的type属性和指定事件目标(event target)的target属性(但是在IE8以及其之前版本中,用的是srcElement而非target)。当然,不同类型的事件还会为其相关事件对象定义一些其他的独有属性。例如,鼠标事件的相关对象会包含鼠标指针的坐标,而键盘事件的相关对象会包含按下的键和辅助键的详细信息。
以上说完了四个基本概念。那么问题来了——如果在一个web页面上用鼠标点击一个元素a的某一子元素b时,应该先执行子元素b注册的事件处理程序还是先执行元素a注册的事件处理程序呢(假设元素a和它的子元素b都有注册事件处理程序)?身为读者的你是否想过这个问题呢?
这个问题就涉及到浏览器中的事件传播(event propagation)机制。相信大家都听说过事件冒泡(event bubble)和事件捕获(event capturing)吧!没错,它们就是浏览器中的事件传播机制。无图无真相,没有配图?那怎么阔以:

看了图之后相信你已经大概理解了浏览器中的事件传播机制了:当一个事件发生时,它会先从浏览器顶级对象Window一路向下传递,一直传递到触发这个事件的那个元素,这也就是事件捕获过程。然而,一切并没有结束,事件又从这个元素一路向上传递到Window对象,这也就是事件冒泡过程(但是在IE8以及其之前版本中,事件模型并未定义捕获过程,只有冒泡过程)。
所以,关于上面的问题,还得看元素a注册的事件处理程序是在捕获过程还是在冒泡过程了。那么到底什么是在捕获过程注册事件处理程序,在冒泡过程注册事件处理程序又是怎么做的呢?这就得好好说说几种注册事件处理程序的方式了:
1. 设置HTML标签属性为事件处理程序
文档元素的事件处理程序属性,其名字由“on”后面跟着事件名组成,例如:onclick、onmouseover。当然了,这种形式只能为DOM元素注册事件处理程序。实例:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow:hidden;}
#div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
#div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
</style>
</head>
<body>
<div id="div1" onClick="console.log('div1');">div1
<div id="div2" oNClick="console.log('div2');">div2
<div id="div3" onclick="console.log('div3');" onclick="console.log('div3333');">div3
</div>
</div>
</div>
<script type="text/javascript">
</script>
</body>
</html>
结果(鼠标点击div3区域后):

从结果中可以看出:
①因为HTML里面不区分大小写,所以这里事件处理程序属性名大写、小写、大小混写均可,属性值就是相应事件处理程序的JavaScript代码;
②若给同一元素写多个onclick事件处理属性,浏览器只执行第一个onclick里面的代码,后面的会被忽略;
③这种形式是在事件冒泡过程中注册事件处理程序的;
2.设置JavaScript对象属性为事件处理程序
可以通过设置某一事件目标的事件处理程序属性来为其注册相应的事件处理程序。事件处理程序属性名字由“on”后面跟着事件名组成,例如:onclick、onmouseover。实例:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow:hidden;}
#div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
#div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
</style>
</head>
<body>
<div id="div1">div1
<div id="div2">div2
<div id="div3">div3
</div>
</div>
</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var div3 = document.getElementById('div3');
div1.onclick = function(){
console.log('div1');
};
div2.onclick = function(){
console.log('div2');
};
div3.onclick = function(){
console.log('div3');
};
div1.onclick = function(){
console.log('div11111');
};
div1.onClick = function(){
console.log('DIV11111');
};
</script>
</body>
</html>
结果(鼠标点击div3区域后):

从结果中可以看出:
①因为JavaScript是严格区分大小写的,所以,这种形式下属性名只能按规定小写;
②若给同一元素对象写多个onclick事件处理属性,后面写的会覆盖前面的(ps:这就是在修改一个对象属性的值,属性的值是唯一确定的);
③这种形式也是在事件冒泡过程中注册事件处理程序的;
3.addEventListener()
前两种方式出现在Web初期,众多浏览器都有实现。而addEventListener()方法是标准事件模型中定义的。任何能成为事件目标的对象——这些对象包括Window对象、Document对象和所有文档元素等——都定义了一个名叫addEventListener()的方法,使用这个方法可以为事件目标注册事件处理程序。addEventListener()接受三个参数:第一个参数是要注册处理程序的事件类型,其值是字符串,但并不包括前缀“on”;第二个参数是指当指定类型的事件发生时应该调用的函数;第三个参数是布尔值,其可以忽略(某些旧的浏览器上不能忽略这个参数),默认值为false。这种情况是在事件冒泡过程中注册事件处理程序。当其为true时,就是在事件捕获过程中注册事件处理程序。实例:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow:hidden;}
#div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
#div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
</style>
</head>
<body>
<div id="div1">div1
<div id="div2">div2
<div id="div3">div3
</div>
</div>
</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var div3 = document.getElementById('div3');
div1.addEventListener('click', function(){ console.log('div1-bubble'); }, false);
div2.addEventListener('click', function(){ console.log('div2-bubble'); }, false);
div3.addEventListener('click', function(){ console.log('div3-bubble'); }, false);
div3.addEventListener('click', function(){ console.log('div3-bubble222'); }, false);
div1.addEventListener('click', function(){ console.log('div1-capturing'); }, true);
div2.addEventListener('click', function(){ console.log('div2-capturing'); }, true);
div3.addEventListener('click', function(){ console.log('div3-capturing'); }, true);
</script>
</body>
</html>
结果(鼠标点击div3区域后):

从结果中可以看出:
①addEventListener()第三个参数的作用正如上面所说;
②通过addEventListener()方法给同一对象注册多个同类型的事件,并不会发生忽略或覆盖,而是会按顺序依次执行;
相对addEventListener()的是removeEventListener()方法,它同样有三个参数,前两个参数自然跟addEventListener()的意义一样,而第三个参数也只需跟相应的addEventListener()的第三个参数保持一致即可,同样可以省略,默认值为false。它表示从对象中删除某个事件处理函数。实例:
div1.addEventListener('click', div1BubbleFun, false);
div1.removeEventListener('click', div1BubbleFun, false);
function div1BubbleFun(){
console.log('div1-bubble');
}
4.attachEvent()
但是,IE8以及其之前版本的浏览器并不支持addEventListener()和removeEventListener()。相应的,IE定义了类似的方法attachEvent()和detachEvent()。因为IE8以及其之前版本浏览器也不支持事件捕获,所以attachEvent()并不能注册捕获过程中的事件处理函数,因此attachEvent()和detachEvent()要求只有两个参数:事件类型和事件处理函数。而且,它们的第一个参数使用了带“on”前缀的事件处理程序属性名。实例:
var div1 = document.getElementById('div1');
div1.attachEvent('onclick', div1BubbleFun);
function div1BubbleFun(){
console.log('div1-bubble');
}
相应的,从对象上删除事件处理程序函数使用detachEvent()。例如:
div1.detachEvent('onclick', div1BubbleFun);
到此为止,我们已经说了浏览器中事件传播机制以及各种注册事件处理程序的方法。下面我们就再说说事件处理程序调用时的一些问题吧!
二.事件处理程序的调用
1.事件处理程序的参数:正如前面所说,通常事件对象作为参数传递给事件处理函数,但IE8以及其之前版本的浏览器中全局变量event才是事件对象。所以,我们在写相关代码时应该注意兼容性问题。实例(给页面上id为div1的元素添加点击事件,当点击该元素时在控制台输出事件类型和被点击元素本身):
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow: hidden;}
</style>
</head>
<body>
<div id="div1">div1</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
if(div1.addEventListener){
div1.addEventListener('click', div1Fun, false);
}else if(div1.attachEvent){
div1.attachEvent('onclick', div1Fun);
}
function div1Fun(event){
event = event || window.event;
var target = event.target || event.srcElement;
console.log(event.type);
console.log(target);
}
</script>
</body>
</html>
2.事件处理程序的运行环境:关于事件处理程序的运行环境,也就是在事件处理程序中调用上下文(this值)的指向问题,可以看下面四个实例。
实例一:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow: hidden;}
</style>
</head>
<body>
<div id="div1" onclick="console.log('html:'); console.log(this);">div1</div>
<script type="text/javascript">
</script>
</body>
</html>
结果一:

从结果可以看出:
①第一种方法事件处理程序中this指向这个元素本身;
实例二:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow: hidden;}
</style>
</head>
<body>
<div id="div1" onclick="console.log('html:'); console.log(this);">div1</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
div1.onclick = function(){
console.log('div1.onclick:');
console.log(this);
};
</script>
</body>
</html>
结果二:

从结果可以看出:
①第二种方法事件处理程序中this也指向这个元素本身;
②存在第二种方法时,它会覆盖第一种方法注册的事件处理程序;
实例三:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow: hidden;}
</style>
</head>
<body>
<div id="div1" onclick="console.log('html:'); console.log(this);">div1</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
div1.onclick = function(){
console.log('div1.onclick:');
console.log(this);
};
div1.addEventListener('click', function(){
console.log('div1.addEventListener:');
console.log(this);
}, false);
</script>
</body>
</html>
结果三:

从结果可以看出:
①第三种方法事件处理程序中this也指向这个元素本身;
②第三种方法并不会覆盖第一种或第二种方法注册的事件处理程序;
实例四:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow: hidden;}
</style>
</head>
<body>
<div id="div1" onclick="console.log('html:'); console.log(this);">div1</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
div1.onclick = function(){
console.log('div1.onclick:');
console.log(this);
};
div1.attachEvent('onclick', function(){
console.log('div1.attachEvent:');
console.log(this === window);
}); </script>
</body>
</html>
结果四:

从结果可以看出:
①第四种方法事件处理程序中this指向全局对象Window;
②第四种方法也不会覆盖第一种或第二种方法注册的事件处理程序;
3.事件处理程序的调用顺序:多个事件处理程序调用规则如下:
①通过HTML属性注册的处理程序和通过设置对象属性的处理程序一直优先调用;
②使用addEventListener()注册的处理程序按照它们的注册顺序依次调用;
③使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序;
4.事件取消:
①取消事件的浏览器默认操作(比如点击超链接元素会自动发生页面跳转的默认操作):如果使用前两种方法注册事件处理程序,可以在处理程序中添加返回值false来取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也可以通过调用事件对象的preventDefault()方法取消事件的默认操作。至于IE8及其之前的浏览器可以通过设置事件对象的returnValue属性为false来取消事件的默认操作。参考代码:
function cancelHandler(event){
var event = event || window.event;
if(event.preventDefault){
event.preventDefault();
}
if(event.returnValue){
event.returnValue = false;
}
return false;
}
②取消事件传播:在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法阻止事件的继续传播,它能工作在事件传播期间的任何阶段(捕获期阶段、事件目标本身、冒泡阶段);但是在IE8以及其之前版本的浏览器中并不支持stopPropagation()方法,而且这些浏览器也不支持事件传播的捕获阶段,相应的,IE事件对象有一个cancelBubble属性,设置这个属性为true能阻止事件进一步传播(即阻止其冒泡)。参考代码(阻止发生在div3区域的点击事件冒泡到div2和div1):
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>test</title>
<style type="text/css">
#div1{width: 300px; height: 300px; background: red; overflow:hidden;}
#div2{margin:50px auto; width: 200px; height: 200px; background: green; overflow:hidden;}
#div3{margin:50px auto; width: 100px; height: 100px; background: blue;}
</style>
</head>
<body>
<div id="div1">div1
<div id="div2">div2
<div id="div3">div3
</div>
</div>
</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var div3 = document.getElementById('div3');
div1.onclick = function(){
console.log('div1');
};
div2.onclick = function(){
console.log('div2');
};
div3.onclick = function(event){
stopEventPropagation(event);
console.log('div3');
}; function stopEventPropagation(event){
var event = event || window.event;
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble = true;
}
}
</script>
</body>
</html>
当然,关于事件冒泡还是有可利用之处的,这也就是我们常说的事件代理或者事件委托,我之前曾写过一篇文章介绍相关内容,详情请看:http://www.cnblogs.com/craftsman-gao/p/4811845.html 。
下面是在下衷心送给各位看官的五个欢迎(O(∩_∩)O~):欢迎转载、欢迎收藏、欢迎评论、欢迎点赞、欢迎推荐!
JavaScript之事件处理详解的更多相关文章
- 从mixin到new和prototype:Javascript原型机制详解
从mixin到new和prototype:Javascript原型机制详解 这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...
- JavaScript严格模式详解
转载自阮一峰的博客 Javascript 严格模式详解 作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...
- iOS开发——实用技术OC篇&事件处理详解
事件处理详解 一:事件处理 事件处理常见属性: 事件类型 @property(nonatomic,readonly) UIEventType type; @property(nonatomic ...
- [转]javascript console 函数详解 js开发调试的利器
javascript console 函数详解 js开发调试的利器 分步阅读 Console 是用于显示 JS和 DOM 对象信息的单独窗口.并且向 JS 中注入1个 console 对象,使用该 ...
- javascript 节点属性详解
javascript 节点属性详解 根据 DOM,html 文档中的每个成分都是一个节点 DOM 是这样规定的:整个文档是一个文档节点每个 html 标签是一个元素节点包含在于 html 元素中的文本 ...
- (" use strict")Javascript 严格模式详解
Javascript 严格模式详解 转载别人的博客内容,浏览了一遍,没有全部吸收,先保存一下链接 http://www.ruanyifeng.com/blog/2013/01/javascript_s ...
- 【HANA系列】SAP HANA XS使用JavaScript数据交互详解
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用Jav ...
- JavaScript运行机制详解
JavaScript运行机制详解 var test = function(){ alert("test"); } var test2 = function(){ alert(& ...
- [转]JavaScript异步机制详解
原文: https://www.jianshu.com/p/4ea4ee713ead --------------------------------------------------------- ...
随机推荐
- 【SICP感应】1 工艺和替代模式
<计算机程序的构造和解释>这本书的目的不是要解释的编程语言的语法,它是一种. 不是在你的语句知识,但是,你要教的东西做什么. 这是一个过程,一个精神. 就是所谓的程序规则的模式. 书中用了 ...
- Python验证码识别处理实例(转)
一.准备工作与代码实例 1.PIL.pytesser.tesseract (1)安装PIL:下载地址:http://www.pythonware.com/products/pil/(CSDN下载) 下 ...
- 【游戏】2048及各种变种大集合汇总【更新ing~新版Floppy2048 - 恒星聚变版 - 恶搞改数据】
threes - 鼻祖 手机版:http://asherv.com/threes/ js版:http://threesjs.com/ 2048 - 原版 http://gabrielecirulli. ...
- python正文(两)
在本文中,我读了记录和总结<Python标准库>一本书,本节课文的学习和理解. 事实上,在Python于,使用一些方法这段文字是一回事,尤其是经常使用.在一般情况下,会用String这样的 ...
- Visual Prolog 的 Web 专家系统 (7)
GENI核心 -- 推理引擎(1)知识表示 GOAL最后一句是谓语infer(),它的含义是"论证". 因此,,进GENI核心,执行视图推理引擎. infer() infer(): ...
- Android 导入第三方jar时 出现ClassNotFoundException
处理方式: 1.首先检查有没有正确的导入该jar和有没有重复导入该jar. 2.如果没有出现1的问题,那么在buildpath->order and export 中将你导入的jar左边的复选框 ...
- 自定义错误页面mvc用法
原谅我这个新手,对大神们来说这么简单的问题,竟折腾了我一个上午,仅此文章做个记录,供以后备用. 自定义错误页面(custom error pages)在asp.net webform里的配置请看htt ...
- 2014在百度之星程序设计大赛 - 资格 第四个问题 Labyrinth
小记:dfs暂停,不是决定性的 思维:由于只有三个方向向上和向下和向右,然后,我对待每列从左至右.然后,当在下一列的上一列的处理再加工每个值去获得正确的值,保存各坐标的数组格你可以得到最大值.每处理完 ...
- 【转】Directx11 SDK文档
原文地址:http://blog.csdn.net/cmt100/article/details/6343274 总结 这是一个初步的教程.我们将通过必要的步骤来创建一个Win32 Applicati ...
- asp.net模板控件示例
原文:asp.net模板控件示例 模板控件允许将控件数据与其表示形式相分离,模板化控件不提供用户界面. 编写它则是为了实现一个命名容器以及包含属性和方法可由宿主页访问的类,MSDN是这样解释的. 下面 ...