JavaScript 事件流、事件处理程序及事件对象总结


Posted in Javascript onApril 01, 2017

JS与HTML之间的交互通过事件实现。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式,支持页面的行为与页面的外观之间的松散耦合。本文将介绍JS事件相关的基础知识。

一、事件流

事件流描述的是从页面中接受事件的顺序。

事件冒泡

事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的结点(文档)。以下面HTML页面为例,如果你点击了页面中的按钮,那么”click”事件会按照< button>、< body>、< html>、document的顺序传播。换句话说,事件冒泡指的就是事件从底层触发事件的元素开始沿着DOM树向上传播,直到document对象。

<html>
 <head>
  <title>Test</title>
 </head>
 <body>
  <button id="myBtn">A Btn</button>
 </body>
</html>

事件捕获

与事件冒泡的思路相反,事件捕获的思想是不太具体的节点应该更早地接收到事件,最具体的结点应该最后才接收事件。同样还是上面那个例子,点击页面中的按钮之后,”click”事件会按照document、< html>、< body>、< button>的顺序传播。换句话说,事件捕获就是指事件从document对象开始沿着DOM树向下传播,直到事件的实际目标元素。

DOM事件流

“DOM2级事件”规定的事件包括三个阶段: 事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

还是以之前的点击按钮为例,在DOM事件流中,捕获阶段,”click”事件从document开始向下传递到body元素(注意,实际目标button在捕获阶段不会接收到事件)。目标阶段,button元素接收到”click”事件。最后,冒泡阶段,事件又被传播回文档。

二、事件处理程序

事件是用户或浏览器自身执行的某种动作,而响应某个事件的函数就叫做事件处理程序或事件侦听器。

HTML事件处理程序

这里的HTML事件处理程序指的是直接在HTML元素里面通过特性(attribute)定义的事件处理程序,请看下面的代码示例。这样是定的事件处理程序会创建一个封装着元素属性值的函数,this值等于事件的目标元素。通过这种方法指定事件处理程序存在不少缺点,不推荐使用。

<button onclick="alert('HaHa~')">Btn-1</button>
<button onclick="alert('event.type')">Btn-2</button>
<button onclick="handler()">Btn-3</button>
<script type="text/javascript">
 function handler() {
  alert("Haha~");
 }
</script>

DOM0级事件处理程序

通过JS指定事件处理程序的传统方式就是将一个函数赋值给一个事件处理程序属性,请看下面代码示例。通过这种方式指定的事件处理程序是在元素的作用域中运行,this引用的是当前元素。这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。若要删除事件,直接令onclick的值为空即可。

var btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log("this.id"); // "myBtn"
};
// 删除事件处理程序
btn.onclick = null;

DOM2级事件处理程序

“DOM2级事件”定义了两个方法用于指定和删除事件处理程序,addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法。这两个方法都接收3个参数,要处理的事件、处理函数、布尔值。最后的布尔值为true时表示在捕获阶段调用事件处理程序,为false时表示在冒泡阶段调用处理程序。与DOM0级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。DOM2级方法添加事件处理程序的优势是可以添加多个事件处理程序。这些事件处理程序会按照它们被添加的顺序触发。下面是代码示例:

var btn = document.getElementById("myBtn");
// 添加,触发点击事件时先输出"myBtn"再输出"HaHa~"
btn.addEventListener("click", function() {
 console.log(this.id);
}, false);
btn.addEventListener("click", function() {
 console.log("HaHa~");
}, false);

通过addEventListener()添加的事件只能通过removeEventListener()来删除。删除时传入的参数与添加时使用的参数应该保持一致。这也意味着通过addEventListener()添加的匿名函数将无法删除,因为无法将添加时传递的匿名函数传给removeEventListener(),即便在删除的时候写了一个一模一样的函数,但此时这个函数只是一个新的匿名函数。请看下面代码示例:

var btn = document.getElementById("myBtn");
// 无法删除匿名函数
btn.addEventListener("click", function() {
 console.log(this.id);
}, false);
btn.removeEventListener("click", function() {
 console.log(this.id);
}, false);

// 正确的添加和删除方式
function handler() {
 console.log(this.id);
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候才将事件处理程序添加到捕获阶段。JS高级程序设计上给出的建议是,如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。

IE事件处理程序

IE实现了与DOM中类似的两个方法: attachEvent()和deleteEvent()。这两个方法接收两个参数,事件处理程序名称和事件处理程序。注意,第一个参数是事件处理程序名称而不是事件名称,也就是说在注册点击事件的处理程序时应该传入”onclick”而不是”click”,这里跟DOM的方法有些差别。另外,这两个方法注册的事件处理程序是在全局作用域中运行而不是元素作用域,this的值指向window。还有一点需要特别小心,通过attachEvent()方法也可以添加多个事件处理程序,但是它们的执行顺序却不是按照它们被添加的顺序,而是完全相反,跟DOM方法截然不同。突然觉得IE真的特别反人类~~~下面是代码示例:

var btn = document.getElementById("myBtn");
function handler1() { // ... }
function handler2() { // ... }
// 添加,触发点击事件时先执行handler2再执行handler1
btn.attachEvent("onclick", handler1);
btn.attachEvent("onclick", handler2);
// 删除
btn.deleteEvent("onclick", handler1);
btn.deleteEvent("onclick", handler2);

三、事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息,包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。

DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入事件处理程序中,无论指定事件处理程序时用的是DOM0还是DOM2的方法,都会传入event对象。event对象只有在事件处理程序执行期间才会存在,一旦事件处理程序执行完毕,event对象就会被销毁。下面是代码示例:

var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log(event.type); // "click"
}
btn.addEventListener("click", function(event) {
 console.log(event.type);
}, false);

event对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性方法也有所不同。但是所有的事件都会有下列的属性或方法:

  • bubbles: 布尔值,表示事件是否冒泡
  • cancelable: 布尔值,表示是否可以取消事件的默认行为
  • currentTarget: 元素,事件处理程序当前正在处理事件的那个元素
  • defaultPrevented: 布尔值,表示是否调用过preventDefault()方法
  • detail: 整数,与事件相关的细节信息
  • eventPhase: 整数,调用事件处理程序的阶段,1表示捕获阶段,2表示目标阶段,3表示冒泡阶段
  • preventDefault(): 函数,取消事件的默认行为,cancelable为true时可以调用该方法
  • stopImmediatePropagation(): 函数,取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用
  • stopPropagation(): 函数,取消事件的进一步捕获或冒泡,bubbles为true时可以调用这个方法
  • target: 元素,事件的目标
  • trusted: 布尔值,为true时表示事件是浏览器生成的,否则表示事件是通过JS创建的
  • type: 字符串,被触发的事件类型
  • view: 与事件关联的抽象视图,等同于发生事件的window对象

下面代码示例展示了上述部分属性的用法,也可以帮助我们进一步理解事件流。假设页面中有一个按钮”myBtn”。当点击按钮时,this和currentTarget都等于body元素,因为事件处理程序是注册在body元素上。target的值却等于按钮元素,因为它是click事件的真正目标。由于按钮上没有注册事件处理程序,结果”click”事件冒泡到了document.body那里才得到处理。

document.body.onclick = function(event) {
 console.log(event.currentTarget === document.body); // true
 console.log(this === document.body); // true
 console.log(event.target === document.getElementById("myBtn")); // true
};

再看一个例子,下面代码中,stopPropagation()方法取消了事件的进一步捕获或冒泡。当我点击按钮时,本来应该会因为事件冒泡机制触发按钮和body元素上的点击事件处理程序,输出”From Bth …”和”From Body …”。现在点击事件在按钮元素上触发之后就被阻止继续在DOM层次中的传播,因此body上的事件处理程序不会被触发。

var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
 console.log("From Bth ...");
 event.stopPropagation(); // 停止事件传播
};
document.body.onclick = function() {
 console.log("From Body ...");
};

IE中的事件对象

在IE中,使用DOM0的方法添加事件处理程序时,event对象作为window对象的一个属性存在。如果是通过attachEvent()方法添加,则event对象是作为参数传入事件处理函数。下面是代码示例:

var btn = document.getElementById("myBtn");
btn.onclick = function() {
 var event = window.event;
 console.log(event.type); // "click"
};
btn.attachEvent("onclick", function(event) {
 console.log(event.type); // "click"
});

IE的event对象同样也包含与创建它的事件相关的属性和方法,这些属性和方法也会因为事件类型的不同而有所差异。但所有事件对象都会包含下列属性:

  • cancelBubble: 布尔值,可读可写,默认为false。将其设置为true时取消事件冒泡
  • returnValue: 布尔值,可读可写,默认为true。将其设置为false时取消事件的默认行为
  • srcElment: 元素,事件的目标元素,与DOM中的target属性相同
  • type: 字符串,事件类型

在IE中,事件处理程序的作用域是根据指定它的方式来确定,this的值不一定是指向事件的目标元素。因此,使用srcElement属性更具保险。请看下面代码实例,第一种方式中this的值为目标元素,而第二种方式,前面讲过这种方式的事件处理程序是在全局作用域中执行,因此this的值为window。

var btn = document.getElementById("myBtn");
btn.onclick = function() {
 console.log(window.event.srcElement === this); // true
}
btn.attachEvent("onclick", function(event) {
 console.log(event.srcElement === this); // false
});

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
YUI 读码日记之 YAHOO.util.Dom - Part.1
Mar 22 Javascript
基于jquery编写的横向自适应幻灯片切换特效的实例代码
Aug 06 Javascript
如何将网页表格内容导入excel
Feb 18 Javascript
基于js实现checkbox批量选中操作
Nov 22 Javascript
jQuery表格的维护和删除操作
Feb 03 Javascript
JavaScript限定范围拖拽及自定义滚动条应用(3)
May 17 Javascript
Node 自动化部署的方法
Oct 17 Javascript
vue中element组件样式修改无效的解决方法
Feb 03 Javascript
JS实现的图片选择顺序切换和循环切换功能示例【测试可用】
Dec 28 Javascript
详解vue 动态加载并注册组件且通过 render动态创建该组件
May 30 Javascript
vue通过video.js解决m3u8视频播放格式的方法
Jul 30 Javascript
微信小程序吸底区域适配iPhoneX的实现
Apr 09 Javascript
ES6新特性之字符串的扩展实例分析
Apr 01 #Javascript
Vue.js实战之利用vue-router实现跳转页面
Apr 01 #Javascript
ES6新特性之函数的扩展实例详解
Apr 01 #Javascript
vue-cli的webpack模板项目配置文件分析
Apr 01 #Javascript
ES6新特性之模块Module用法详解
Apr 01 #Javascript
Vue.js实战之组件之间的数据传递
Apr 01 #Javascript
ES6新特性之解构、参数、模块和记号用法示例
Apr 01 #Javascript
You might like
PHP 开发环境配置(Zend Server安装)
2010/04/28 PHP
第四章 php数学运算
2011/12/30 PHP
php的数组与字符串的转换函数整理汇总
2013/07/18 PHP
用php守护另一个php进程的例子
2015/02/13 PHP
Laravel5.1 框架路由基础详解
2020/01/04 PHP
JQuery Tips(4) 一些关于提高JQuery性能的Tips
2009/12/19 Javascript
node.js中的fs.rmdirSync方法使用说明
2014/12/16 Javascript
jQuery在header中设置请求信息的方法
2017/03/06 Javascript
解决vue 格式化银行卡(信用卡)每4位一个符号隔断的问题
2018/09/14 Javascript
JS实现的获取银行卡号归属地及银行卡类型操作示例
2019/01/08 Javascript
JS如何实现动态添加的元素绑定事件
2019/11/12 Javascript
解决vue-router 切换tab标签关闭时缓存问题
2020/07/22 Javascript
[01:13:59]LGD vs Mineski Supermajor 胜者组 BO3 第三场 6.5
2018/06/06 DOTA
python操作xml文件详细介绍
2014/06/09 Python
Python中的面向对象编程详解(上)
2015/04/13 Python
Python处理XML格式数据的方法详解
2017/03/21 Python
python里使用正则表达式的组嵌套实例详解
2017/10/24 Python
python enumerate函数的使用方法总结
2017/11/15 Python
《Python学习手册》学习总结
2018/01/17 Python
pandas的object对象转时间对象的方法
2018/04/11 Python
PyQt5 QSerialPort子线程操作的实现
2018/04/21 Python
python selenium爬取斗鱼所有直播房间信息过程详解
2019/08/09 Python
python将四元数变换为旋转矩阵的实例
2019/12/04 Python
使用python3批量下载rbsp数据的示例代码
2019/12/20 Python
Python+opencv+pyaudio实现带声音屏幕录制
2019/12/23 Python
香港迪士尼乐园酒店预订:Hong Kong Disneyland Hotels
2017/05/02 全球购物
全球游戏Keys和卡片市场:GamesDeal
2018/03/28 全球购物
当我正在为表建立索引的时候,SQL Server 会禁止对表的访问吗
2014/04/28 面试题
小学教师培训感言
2014/02/11 职场文书
学习雷锋倡议书
2014/04/15 职场文书
培训研修方案
2014/06/06 职场文书
工伤事故赔偿协议书(标准)
2014/09/29 职场文书
初婚初育证明范本
2014/11/24 职场文书
奥巴马开学演讲观后感
2015/06/12 职场文书
Python基础之Socket通信原理
2021/04/22 Python
解析mybatis-plus中的resultMap简单使用
2021/11/23 Java/Android