彻底理解H5的DOM事件
我们大家都知道,人与人之间的交流可以通过语言,文字,肢体动作,面部微表情等,但是你知道Javascript和HTML之间是通过什么进行交互的么?你又知道Javascript和HTML之间是如何进行交互的么?如果你不是那么清楚,可以看这篇文章。。。
前面的话:
这篇博文由浅入深,先介绍两种事件流,然后介绍常见的事件处理程序以及它们之间的差异(涉及到一个简单的兼容的处理函数),后面还会针对事件对象进行深入学习,最后则是通过DOM事件实现的小案例。
事件流:
关于事件流,简单的来说就是:事件流描述的是从页面中接受事件的顺序。下面是《Javascript高级程序设计》里面的说法:
当浏览器发展到第四代时(IE4和Netscape Communicator 4),浏览器团队遇到一个很有意思的问题:页面的哪一部分会拥有特定的事件?想象下在一张纸上有一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是一组圆。两家公司的开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,那么同时你也单击了按钮的容器元素,甚至整个页面。
事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了两个截然相反的事件流概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。看下面的例子
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件流</title>
</head>
<body>
<input type="button" id="btn" value="" />
</body>
</html>
点击按钮,那么这个click事件会按照这样传播:
<input-->body-->html-->document
事件捕获
Netscape团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
针对上面同样的例子,点击按钮,那么此时click事件会按照这样传播:
document-->html-->body-->input
虽然事件捕获是Netscape唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持这种事件流模型。但由于老版本的浏览器不支持,因此很少有人使用事件捕获。
事件处理程序
HTML事件处理程序
HTML事件处理程序(现在不建议使用了):事件直接加在HTML代码中,例如:
<input type="button" id="btn" value="HTML事件处理程序" onclick="alert('HTML事件处理程序')" />
或者:
<input type="button" id="btn1" value="HTML事件处理程序" onclick="demo1()" />
function demo1(){
alert("HTML事件处理程序");
}
缺点:HTML和js代码高耦合,如果修改,就要修改两个地方--HTML元素内和script函数。
DOM0级事件处理程序
DOM0级事件处理程序,是一种较传统的方式:把一个函数赋值给一个事件的处理程序属性。例如:
var btn2 = document.getElementById("btn2");
btn2.onclick = function(){
alert("DOM0级事件处理程序");
}
现在用得比较多,优势是简单(只需要考虑JS不考虑HTML)且跨浏览器。如果想取消点击事件,将其设为null即可。
var btn2.onclick = null;//这时点击不会弹出窗口,表示已经取消了该点击事件
下面是我学习时候的一些思考:
思考一:DOM0级事件是否可以同时添加多个不同事件处理方法
var btn2 = document.getElementById("btn2");
btn2.onclick = function(){
console.log("DOM0级事件处理程序-click-01");
}
btn2.onmouseover = function(){
console.log("DOM0级事件处理程序-mouseover");
}
结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-01。
说明:DOM0级事件可以同时添加多个不同事件处理方法
思考二:DOM0级事件是否可以同时添加多个相同事件处理方法
我们在前面的基础上添加如下代码:
btn2.onclick = function(){
console.log("DOM0级事件处理程序-click-02");
}
结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-02。(在这里你可能以为会出现DOM0级事件处理程序-click-01和DOM0级事件处理程序-click-02)
说明:DOM0级事件不可以同时添加多个相同事件处理方法(这样说好像不太严格,大概意思就是直接忽略了前面的onclick,只显示最后的onclick的结果。
那么这里就设下一个疑问:我们应该如何让DOM同时绑定多个相同事件呢???我们接着往下看
DOM2级事件处理程序
DOM2级事件处理程序定义了两个方法--用于处理指定和删除事件处理程序的操作:
addEventListener()添加事件监听程序
removeEventListener()移除事件监听程序
三个参数设置;事件名称,处理方法(函数),布尔值--false:表示在冒泡阶段调用事件处理程序(可以最大限度的兼容浏览器),一般设为false;true表示在捕获阶段调用事件处理程序
来看下面的例子:
var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
console.log("DOM2级事件处理程序-click-01");
},false)
//DOM2级事件处理程序-click-01
与DOM0级不同的是,我们移除需要使用removeEventListener(),代码如下:
var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
console.log("DOM2级事件处理程序-click-01");
},false)
btn3.removeEventListener('click',function(){
console.log("DOM2级事件处理程序-click-移除成功");
},false)
//DOM2级事件处理程序-click-01
在这里,你很有可能和我想的一样,DOM2级事件处理程序已经移除完了。但是结果是DOM2级事件处理程序-click-01依然还在,按照理论上来说不应该是被移除了么?但是为什么还在?
原因:通过addEventListener方法添加的事件处理程序只能用removeEventListener来移除,移除时传入的参数与添加处理程序时使用的参数相同。这就意味着通过addEventListener添加的匿名函数将无法移除。真的是这样么?我们继续来看例子:
var btn3 = document.getElementById("btn3");
var handler = function demo3(){
alert("DOM2级事件处理程序");
}
btn3.addEventListener('click',handler,false)
//在不加后面的代码的时候弹框显示DOM2级事件处理程序
btn3.removeEventListener('click',handler,false)
//加了removeEventListener后点击,没有弹框,说明该点击事件已经被移除了
接下来我们同样思考几个问题:
思考一:DOM2级事件是否可以同时添加多个不同事件处理方法
var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
console.log("DOM2级事件处理程序-click-01");
},false)
btn3.addEventListener('mouseover',function(){
console.log("DOM2级事件处理程序-mouseover");
},false)
结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01。
说明:DOM2级事件可以同时添加多个不同事件处理方法
思考二:DOM2级事件是否可以同时添加多个相同事件处理方法
var btn3 = document.getElementById("btn3");
btn3.addEventListener('click',function(){
console.log("DOM2级事件处理程序-click-01");
},false)
btn3.addEventListener('mouseover',function(){
console.log("DOM2级事件处理程序-mouseover");
},false)
btn3.addEventListener('click',function(){
console.log(this.id);
},false)
结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01和btn3。
说明:DOM2级事件可以同时添加多个不同事件处理方法,同时在这里可以用this引用被触发的元素。
注意:
1、要处理的事件名(事件名去掉“on”,如点击事件需写成click);
2、作为事件处理程序的函数名(函数不需要带括号,只要函数名或者直接写函数体也可以);
3、布尔值(true表示使用事件捕获流,false表示使用事件冒泡流,一般使用false兼容各种浏览器)。
4、通过addEventListener添加的事件只能通过removeEventListener来移除;DOM 0级和DOM 2级事件可以在一个DOM上添加多个事件,按顺序执行
5、DOM2级事件处理程序适用于:IE9、Firefox、Safari、Chrome和Opera
思考:那么如果我们想要兼容低版本的IE该怎么办呢?请继续往下阅读
IE事件处理程序
(声明:IE事件处理程序的代码没有进行测试)
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),这两个方法接受相同的两个参数:事件处理程序名称与事件处理函数程序函数(因为IE8及更早版本只支持时间冒泡,所以没有第三个参数)。下面我们来看代码:
var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
console.log("IE事件处理程序-onclick-01");
});
这里需要注意的是,第一个参数是onclick,而不是click。另外与DOM0级中的不同在于作用域,看下面的代码:
var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
console.log(this === window); //true
});
其他需要注意的有:
1、attachEvent方法也可以为一个元素添加一个或多个事件处理程序,当添加多个相同的事件处理程序的时候,这些事件处理程序不是以添加它们的顺序而执行,而是以相反的顺序被触发。比如下面这个例子:
var btn4 = document.getElementById("btn4");
btn4.attachEvent('onclick',function(){
console.log("hello");
});
btn4.attachEvent('onclick',function(){
console.log("World");
});
这段代码用DOM0级的方法得到的结果为World,用DOM2级的方法得到结果为先是hello然后是World,而在这里的结果为先是World然后是hello。
2、detachEvent()方法和DOM2级中移除的方法一样,同样也需要注意中间添加的匿名函数不能被移除,这里就不再举例说明了。
从HTML事件处理程序到DOM0级事件处理程序,再到DOM2级处理程序,还有IE事件处理程序,我们在使用的时候为了让用户体验最佳,我们该让我们的代码兼容各种浏览器,那么我们有什么好的方法让我们的代码兼容到各种浏览器中呢?
跨浏览器的事件处理程序
为了解决跨浏览器的问题,我们不得不集中的把前面几种方法柔和在一起,然后就出现了跨浏览器的事件处理程序。(我的思路如下)
1、先考虑添加事件的情况,在添加的时候,我们要知道给谁添加?添加的是什么事件?然后事件触发的是什么函数?
给谁添加?当然是我们的元素了,因为还不知道,这里我们先设置为element
添加的是什么事件?click?onclick?onmouseover?我们也还是不知道,先设置为type吧
添加什么函数?前面的例子中有用到过handler这里也先就用这个
function(element, type, handler) {……}
2、我们要判断我们的浏览器是否支持相关的操作,这里我们可以用if-else来判断
if(element.addEventListener) {
//DOM2级事件处理程序
} else if(element.attachEvent) {
//IE事件处理程序
} else {
//DOM0级事件处理程序
}
3、针对DOM2级事件处理程序、IE事件处理程序和DOM0级事件处理程序它们有相似的地方,然后我们通过前面可以知道,它们对于事件的处理有点不和谐,我们该怎样呢?很明显,添加"on"比去掉"on"更简单,所以比如点击事件的type应该为click,然后在需要用onclick的地方,使用'on'+type,然后各自保留各自的特点
DOM2级事件处理程序:element.addEventListener(type, handler, false);
IE事件处理程序:element.attachEvent('on' + type, handler);
DOM0级事件处理程序:element['on' + type] = handler;
4、到这里基本的添加情况以完成,接下来用同样的方法来考虑移除事件的情况(步骤省略)
5、然后分别给添加事件函数和移除事件函数进行命名addHandler和deleteHandler,并封装起来,得到我们的最终效果:
var eventUtil = {
addHandler: function(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else if(element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
},
deleteHandler: function(element, type, handler) {
if(element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if(element.detachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
}
}
事件对象
DOM中的事件对象
前面我们已经封装好一个函数了,我们后面的操作直接引用前面的那个函数。说到事件对象,我们先不得不说事件对象的两个重要属性type和target
其中type是获取事件的类型,而target是获取事件的目标。直接看代码:
var btn = document.getElementById("btn");
eventUtil.addHandler(btn,'click',showMes);
function showMes(event){
alert(event.type);//结果是弹出click
}
结果是弹出click,说明了该事件的类型是点击事件
var btn = document.getElementById("btn");
eventUtil.addHandler(btn,'click',showMes);
function showMes(event){
alert(event.target);//结果是弹出[object HTMLInputElement]
}
结果是弹出[object HTMLInputElement],说明该事件的目标是input元素
下面我们运行一段代码,来看一个有趣的现象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<div id= "box" > <input type= "button" id= "btn" value= "DOM中的事件对象" /> <a href= "www://baidu.com" id= "go" >百度</a> </div> function showMes( event ){ alert( event .target.nodeName); } function showBox(){ alert( "这是放盒子的box" ) } var btn = document.getElementById( "btn" ); var box = document.getElementById( "box" ); eventUtil.addHandler(btn, 'click' ,showMes); eventUtil.addHandler(box, 'click' ,showBox); |
运行结果:鼠标点击按钮,分别弹出了“INPUT”和“这是放盒子的box”,我们只点击了按钮,为什么按钮所在的div也会显示呢?查资料知道这是因为事件冒泡机制导致的。
那么我们该如何阻止事件冒泡呢?
我们的事件对象给我们提供了一种方法:通过stopPropagation()方法来实现,代码示例如下:
function showMes(event){
alert(event.target.nodeName);
event.stopPropagation();//阻止事件冒泡
} function showBox(){
alert("这是放盒子的box")
} var btn = document.getElementById("btn");
var box = document.getElementById("box"); eventUtil.addHandler(btn,'click',showMes);
eventUtil.addHandler(box,'click',showBox);
光知道阻止事件冒泡行为还不行我们还需要知道的一种方法:阻止事件默认行为-->preventDefault()方法
比如我们知道的a标签有一个默认行为就是跳转到目标地址,如果我们想要阻止这种行为我们就可以使用该方法,代码如下:
<a href="www://baidu.com" id="go">百度</a> var go = document.getElementById("go");
function showGo(event){
event.stopPropagation();//阻止事件冒泡
event.preventDefault();//阻止事件的默认行为
}
eventUtil.addHandler(go,'click',showGo);
运行结果是,无论怎样点击,都跳转不到百度页面。
在a标签里面阻止事件默认行为还有一种方法就是直接在a标签里面添加,比如<a href="javascript:;">aa</a>
补充:在需要通过一个函数处理多个事件时,可以使用type属性,比如:
var btn = document.getElementById("btn");
var handler = function(event){
switch(event.type){
case"click":
alert("Clicked");
break; case"mouseover":
event.target.style.backgroundColor = "red";
break; case"mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
IE中的事件对象
【type属性】用于获取事件类型
【srcElement属性】用于获取事件的目标(DOM中用的是target),所以,可以使用
event=event || window.event;(考虑IE8以前的版本)
var ele=event.target event.srcElement;(考虑非IE与IE)
【cancelBubble属性】用于阻止事件冒泡,设为true表示阻止冒泡,false表示不阻止冒泡。
【returnValue属性】用于阻止事件的默认行为,设为false表示阻止。
这里只介绍IE事件对象的常用属性,关于它的具体例子就不做介绍了
跨浏览器的事件对象:
搞清楚前面的问题,下面的一段代码看起来就简单了。它直接将前面提到的所有方法和属性一起封装起来,并保存到一个单独的js文件中,命名为event.js,其代码如下:
var eventUtil = {
// 添加句柄
addHandler: function(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false);
} else if(element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
},
// 删除句柄
removeHandler: function(element, type, handler) {
if(element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if(element.detachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
},
//获取事件
getEvent: function(event) {
return event ? event : window.event;
},
//获取事件类型
getType: function(event) {
return event.type;
},
//获取元素
getElement: function(event) {
return event.target || event.srcElement;
},
//阻止事件默认行为
preventDefault: function(event) {
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
//阻止事件冒泡
stopPropagation: function(event) {
if(event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
我们可以用如下代码来测试点击a事件
window.onload=function(){
var go=document.getElementById('go'),
box=document.getElementById('box'); eventUtil.addHandler(box,'click',function(){
alert('我是整个父盒子');
}); eventUtil.addHandler(go,'click',function(e){
//e=eventUtil.getEvent(e);
e=e || window.event;
alert(eventUtil.getElement(e).nodeName);
eventUtil.preventDefault(e);
eventUtil.stopPropagation(e);
}); }
事件类型
因为事件类型比较多,这里主要通过两个具体的案例来展示,不做详细介绍
案例一:用鼠标或键盘事件来控制抽奖。
效果截图:
查看地址:http://sandbox.runjs.cn/show/0pibyaes
案例二:模仿扣扣登陆时的拖曳效果(主要学习其中如何实现拖曳效果的思维)
效果截图:
查看地址:http://sandbox.runjs.cn/show/7mhb7pfw
更多参考资料:
参考书籍:《Javascript高级程序设计(第三版)》
参考博客: https://my.oschina.net/u/1403185/blog/184960
http://www.cnblogs.com/luozhihao/p/5934935.html
http://www.cnblogs.com/xiaohuochai/p/5867195.html
彻底理解H5的DOM事件的更多相关文章
- 理解DOM事件流的三个阶段
本文主要解决两个问题: 1.什么是事件流 2.DOM事件流的三个阶段 事件流之事件冒泡与事件捕获 在浏览器发展的过程中,开发团队遇到了一个问题.那就是页面中的哪一部分拥有特定的事件? 可以想象画在一张 ...
- 从click事件理解DOM事件流
事件流是用来解释页面上的不同元素接受一个事件的顺序,首先要明确两点: 1.一个事件的影响元素可能不止一个(同心圆理论),但目标元素只有一个. 2.如果这些元素都绑定了相同名称的事件函数,我们怎么知道这 ...
- javascript:理解DOM事件
首先,此文不讨论繁琐细节,但是考虑到读者的心灵感受,本着以积极向上的心态,在此还是会列举示例说明. 标题为理解DOM事件,那么在此拿一个简单的点击事件为例,希望大家看到这个例子后能触类旁通. DOM ...
- 深度理解DOM事件(实例)
前言 通过如下两个实例来理解DOM事件 实例1--点击别处关闭浮层 onclick与addEventListener的区别 实例2--点击后颜色一层一个层出现的漂亮的彩虹圈 1 实例1--点击别处关 ...
- DOM事件机制进一步理解
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...
- 怎么理解js中的事件委托
怎么理解js中的事件委托 时间 2015-01-15 00:59:59 SegmentFault 原文 http://segmentfault.com/blog/sunchengli/119000 ...
- DOM事件简介--摘自admin10000
Click.touch.load.drag.change.input.error.risize — 这些都是冗长的DOM(文档对象模型)事件列表的一部分.事件可以在文档(Document)结构的任何部 ...
- Javascript事件模型系列(四)我所理解的javascript自定义事件
被我拖延了将近一个月的javascript事件模型系列终于迎来了第四篇,也是我计划中的最后一篇,说来太惭愧了,本来计划一到两个星期写完的,谁知中间遇到了很多事情,公司的个人的,搞的自己心烦意乱浮躁了一 ...
- 【前端盲点】DOM事件流论证CSS盒模型是否具有厚度
前言 很久没有扯淡了,我们今天来扯淡吧. 我今天思考了一个问题,我们页面的dom树到底是如何渲染的,而CSS盒模型与javascript是否有联系,于是便想到一个问题: CSS的盒模型具有厚度么??? ...
随机推荐
- NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
springMVC 内嵌jetty时,出现了NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet: 添加了 ...
- Tomcat学习笔记(三)
Tomcat连接器 tomcat连接器是tomcat的一个核心组件,在tomcat4中的实现原理如下 1.实现Connector接口 2.创建Request对象 3.创建Response对象 tomc ...
- MAC中如何配置两套android-sdk环境
1 背景介绍 随着android studio的完善和普及,越来越多Android app开发者从Eclipse+ADT环境转到Android Studio(后文统一用AS表示).但是,AS往往需要较 ...
- Android自复制传播APP原理学习(翻译)
Android自复制传播APP原理学习(翻译) 1 背景介绍 论文链接:http://arxiv.org/abs/1511.00444 项目地址:https://github.com/Tribler ...
- shell脚本——项目1
案例名称:系统初始化 背景:10台已装有linux系统的服务器 需求: 1.设置时区同步 2.禁用selinux 3.清空防火墙策略 4.历史命令显示操作时间 5.禁止root远程登录 6.禁止定时任 ...
- hdu 6119 …&&百度之星 T6
小小粉丝度度熊 Problem Description 度度熊喜欢着喵哈哈村的大明星——星星小姐. 为什么度度熊会喜欢星星小姐呢? 首先星星小姐笑起来非常动人,其次星星小姐唱歌也非常好听. 但这都不是 ...
- Winform 模拟Session
背景 在Web中Session的功能很好用,于是想Winform中实现该功能,典型应用场景则是登陆成功后,当一段时间不操作,则该会话过期,提示重新登陆. 资源下载 测试代码 示例说明:登陆进去10s不 ...
- 大(NOIP模拟赛Round #10)
题目描述: 小Z有个n个点的高清大图,每个点有且只有一条单向边的出边.现在你可以翻转其中的一些边,使他从任何一个点都不能通过一些道路走回这个点.为了方便,你只需输出方案数对取模即可.当在两个方案中有任 ...
- UVALIVE 3031 Cable TV Network
题意:求点联通度 首先看了别人的题解还是不晓得只枚举汇点的原因觉得行不通 关于求点联通度的建图方法 转自http://hi.baidu.com/lerroy312/item/5a5f36f2f5bba ...
- Appium+python自动化21-DesiredCapabilities详解【转载】
Appium Desired Capabilities Desired Capabilities 是由 keys 和 values 组成的 JSON 对象. 举个简单例子: { "platf ...