在本文中将阅读到的内容有:
- 什么是事件
- 事件监听
- 事件冒泡 事件捕获
- 事件对象
- 事件代理
- 事件发布订阅
在介绍事件冒泡捕获等理论之前,我们首先要明确什么是事件。
事件是您在编程时系统内发生的动作或者发生的事情——系统会在事件出现时产生或触发某种信号,并且会提供一个自动加载某种动作(列如:运行一些代码)的机制。
在 Web 中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。
几个比较常见的事件举例
更多事件类型可以参考这里
2.1 方法一
每个可用的事件都会有一个事件监听器,监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。
举例说明:
假设界面上有一个
<button>Change color</button>
const btn = document.querySelector('button');
function random(number) {
return Math.floor(Math.random()*(number+1));
}
btn.onclick = function() {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}
我们使用 btn 变量存储 button,并使用了Document.querySelector() 函数。我们也定义了一个返回随机数字的函数。代码第三部分就是事件处理器。btn变量指向 button 元素,在 button 这种对象上可触发一系列的事件,因此也就可以使用事件处理器。我们通过将一个匿名函数(这个赋值函数包括生成随机色并赋值给背景色的代码)赋值给“点击”事件处理器参数,监听“点击”这个事件。
只要点击事件在元素上触发,该段代码就会被执行。即每当用户点击它时,都会运行此段代码
这个btn.onclick还有许多
2.2 方法二 addEventListener() 和removeEventListener()
2.2.1 基本使用方法
新的事件触发机制被定义在 Document Object Model (DOM) Level 2 Events Specification, 这个细则给浏览器提供了一个函数 — addEventListener()。这个函数和事件处理属性是类似的,但是语法略有不同。我们可以重写上面的随机颜色背景代码:
const btn = document.querySelector('button');
function bgChange() {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}
btn.addEventListener('click', bgChange);
//清除不使用的事件处理器
btn.removeEventListener('click', bgChange);
您也可以给同一个监听器注册多个处理器,下面这种方式不能实现这一点:
myElement.onclick = functionA;
myElement.onclick = functionB;
第二行会覆盖第一行,但是下面这种方式就会正常工作了:
myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);
当元素被点击时两个函数都会工作。
2.2.2 addEventListener() 详解
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,Document和Window或者任何其他支持事件的对象 (比如 XMLHttpRequest)。
语法:
target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
参数说明:
type 表示监听事件类型的字符串(详细事件类型参考)
listener 当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象。listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数。
options 可选 一个指定有关 listener 属性的可选参数对象。可用的选项如下:
useCapture 可选 Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。 如果没有指定, useCapture 默认为 false 。
注意:
对于事件目标上的事件监听器来说,事件会处于“目标阶段”,而不是冒泡阶段或者捕获阶段。在目标阶段的事件会触发该元素(即事件目标)上的所有监听器,而不在乎这个监听器到底在注册时useCapture
参数值是true还是false。
使用这些参数的更多列子参考这里
在页面中点击一个元素,事件是从这个元素的祖先元素中逐层传递下来的,这个阶段为事件的捕获阶段。当事件传递到这个元素之后,又会把事件逐成传递回去,直到根元素为止,这个阶段是事件的冒泡阶段。
有时候在事件处理函数内部,您可能会看到一个固定指定名称的参数,例如event,evt或简单的e。 这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。 例如,让我们稍稍重写一遍我们的随机颜色示例:
function bgChange(e) {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
e.target.style.backgroundColor = rndCol;
console.log(e);
}
btn.addEventListener('click', bgChange);
event.target vs event.currentTarget
举例说明这俩者的区别:
<div id="a">
<div id="b">
<div id="c">
<div id="d"></div>
</div>
</div>
</div>
<script>
document.getElementById('a').addEventListener('click', function(e) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('b').addEventListener('click', function(e) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('c').addEventListener('click', function(e) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
document.getElementById('d').addEventListener('click', function(e) {
console.log('target:' + e.target.id + '¤tTarget:' + e.currentTarget.id);
});
</script>
上面事件的绑定都是在冒泡阶段的(因为addEventListener useCapture默认为false,代表在冒泡阶段绑定),当我们点击最里层的元素d的时候,会依次输出:
target:d¤tTarget:d
target:d¤tTarget:c
target:d¤tTarget:b
target:d¤tTarget:a
从输出中我们可以看到,event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget。
如果我们把事件都绑定在捕获阶段,然后还是点击最里层的元素d,这时event.target还依旧会指向d,因为那是引起事件触发的元素,因为event.currentTaget指向事件绑定的元素,所以在捕获阶段,最先来到的元素是a,然后是b,接着是c,最后是d。所以输出的内容如下:
target:d¤tTarget:a
target:d¤tTarget:b
target:d¤tTarget:c
target:d¤tTarget:d
preventDefault()
有时,你会遇到一些情况,你希望事件不执行它的默认行为。 最常见的例子是Web表单,例如自定义注册表单。 当你填写详细信息并按提交按钮时,自然行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(或 相同的页面,如果另一个没有指定。)
使用preventDefault()可以阻止默认行为。
stopPropagation()
准事件对象具有可用的名为 stopPropagation()的函数, 当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。
冒泡还允许我们利用事件委托——这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。
一个很好的例子是一系列列表项,如果你想让每个列表项被点击时弹出一条信息,您可以将click单击事件监听器设置在父元素
上,这样事件就会从列表项冒泡到其父元素
上。
事件代理的优点:
发布-订阅模式里面包含了三个模块,发布者,订阅者和处理中心。这里处理中心相当于报刊办事大厅。发布者相当与某个杂志负责人,他来中心这注册一个的杂志,而订阅者相当于用户,我在中心订阅了这分杂志。每当发布者发布了一期杂志,办事大厅就会通知订阅者来拿新杂志。这样在结合下面的图应该很好理解了。
其实就是将发布者和订阅者解耦了,在实际开发中,经常会遇到某个方法内处理很多的逻辑,最简单的就是直接在方法内直接写。这种是高度耦合的面向过程的写法。对于代码维护不友好。而发布-订阅模式就是将两者分离。我触发了某个事件(这里我们将触发该方法定义为事件),我只向调度中心通知,我并不知道调度中心内会怎么处理,有多少个人响应。我只管通知。而订阅者只管在调度中心订阅,有人调用它才响应。
还有一点就是假设我们有3个js文件,事件触发在a.js内,而响应该事件的在b.js和c.js内,要是用常规调用的方法的话,就要把b.js和c.js的方法传到a.js内。这是一个非常麻烦的操作。而发布-订阅模式是将调度中心挂在了全局,我们只管调用调度中心相应的方法注册和订阅。
参考网页:
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Events
https://www.cnblogs.com/yzhihao/p/9398917.html
https://www.cnblogs.com/suyuanli/p/9655699.html