我们大家都知道,人与人之间的交流可以通过语言,文字,肢体动作,面部微表情等,但是你知道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事件的更多相关文章

  1. 理解DOM事件流的三个阶段

    本文主要解决两个问题: 1.什么是事件流 2.DOM事件流的三个阶段 事件流之事件冒泡与事件捕获 在浏览器发展的过程中,开发团队遇到了一个问题.那就是页面中的哪一部分拥有特定的事件? 可以想象画在一张 ...

  2. 从click事件理解DOM事件流

    事件流是用来解释页面上的不同元素接受一个事件的顺序,首先要明确两点: 1.一个事件的影响元素可能不止一个(同心圆理论),但目标元素只有一个. 2.如果这些元素都绑定了相同名称的事件函数,我们怎么知道这 ...

  3. javascript:理解DOM事件

    首先,此文不讨论繁琐细节,但是考虑到读者的心灵感受,本着以积极向上的心态,在此还是会列举示例说明. ​标题为理解DOM事件,那么在此拿一个简单的点击事件为例,希望大家看到这个例子后能触类旁通. DOM ...

  4. 深度理解DOM事件(实例)

    前言 通过如下两个实例来理解DOM事件 实例1--点击别处关闭浮层 onclick与addEventListener的区别 实例2--点击后颜色一层一个层出现的漂亮的彩虹圈 1  实例1--点击别处关 ...

  5. DOM事件机制进一步理解

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  6. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  7. DOM事件简介--摘自admin10000

    Click.touch.load.drag.change.input.error.risize — 这些都是冗长的DOM(文档对象模型)事件列表的一部分.事件可以在文档(Document)结构的任何部 ...

  8. Javascript事件模型系列(四)我所理解的javascript自定义事件

    被我拖延了将近一个月的javascript事件模型系列终于迎来了第四篇,也是我计划中的最后一篇,说来太惭愧了,本来计划一到两个星期写完的,谁知中间遇到了很多事情,公司的个人的,搞的自己心烦意乱浮躁了一 ...

  9. 【前端盲点】DOM事件流论证CSS盒模型是否具有厚度

    前言 很久没有扯淡了,我们今天来扯淡吧. 我今天思考了一个问题,我们页面的dom树到底是如何渲染的,而CSS盒模型与javascript是否有联系,于是便想到一个问题: CSS的盒模型具有厚度么??? ...

随机推荐

  1. JavaScript之实现单选复选、菜单以及返回顶部实例

    1.单选.复选以及反选实例 其实主要是利用for循环提取标签,然后更改checked属性值实现的 <!DOCTYPE html> <html lang="en"& ...

  2. PHP连接mysql数据库进行增删改查--修稿数据

    <?php $id = $_GET['id']; $db = new Mysqli("localhost","root","root" ...

  3. HDU 4758 Walk Through Squares( AC自动机 + 状态压缩DP )

    题意:给你两个串A,B, 问一个串长为M+N且包含A和B且恰好包含M个R的字符串有多少种组合方式,所有字符串中均只含有字符L和R. dp[i][j][k][S]表示串长为i,有j个R,在自动机中的状态 ...

  4. ASP.NET Core MVC 运行所选代码生成器时出错

    添加Nuget Microsoft.VisualStudio.Web.CodeGeneration.Design Microsoft.EntityFrameworkCore.Tools

  5. ftrace 简介

    ftrace 简介 ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析. 最早 ftrace 是一个 function tracer,仅能够记录内核的函数 ...

  6. nyoj 题目737 合并石子(一)

    石子合并(一) 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述     有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的 ...

  7. CodeForces D. Concatenated Multiples

    http://codeforces.com/contest/1029/problem/D You are given an array aa, consisting of nn positive in ...

  8. MVC从Controller到view进行传值的方法

    这几天基本上都是交接的一些杂事,没有什么工作任务,就有空来回顾一下MVC.虽然工作中也用到了MVC,但已经被微软的架构师设计的找不到MVC的影子了,可能有别的考虑吧,至今还没研究出来.所以,今天就来回 ...

  9. vue cli 3 & webpack-merge & webpack 3 & bug

    vue cli 3 & webpack-merge & webpack & bug bug webpack-merge & bug webpack-merge ??? ...

  10. hdu 1007 Quoit Design (最近点对问题)

    Quoit Design Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...