事件监听
什么是事件监听
事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。
例如:用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图
其中,【鼠标点击】和【鼠标拖拽】就是事件,那么这些事情发生之后,为了让JavaScript可以知道,于是就有了事件监听。
事件监听的方式
addEventListener
是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。
语法:
元素对象.addEventListener("事件类型", 要执行的函数)
元素对象:
也就是检测谁被执行了事件。只有我们指定的元素对象执行了指定的事件,事件监听才会执行。
事件类型:
用在网页中的操作有非常多种,事件类型用于指定只有执行了哪一个事件,事件监听才会被触发。
要执行的函数:
当我们的事件监听检测到目标事件发生,那么就需要做出相应的反应,此处的反应,就是执行这个函数。
完成事件监听分成以下步骤:
- 获取 DOM 对象
- 通过
addEventListener
方法为 DOM 对象添加事件监听 - 等待事件触发
- 事件触发后,执行相对应的函数
接下来我为大家展示一段代码(省略了CSS修饰):
<div class="skyblue"></div> <div class="pink"></div> <div class="gold"></div> <div class="palegreen"></div> <script> const gold = document.querySelector(".gold"); gold.addEventListener("click", function () { gold.style.backgroundColor = "red"; }) </script>
在script中,就是一个完整的事件监听过程:
第一步:
const gold = document.querySelector(".gold");
这个过程是在获取gold颜色对应的盒子,即获取DOM对象
第二步:
gold.addEventListener()
,这一步是在事件监听,监听的对象是gold颜色的盒子;
"click"
,代表点击事件,即这个事件监听的触发条件是点击,触发对象的gold颜色的盒子。即:点击gold颜色的盒子后,执行事件监听绑定的函数。
function () {gold.style.backgroundColor = "red";}
,这是被绑定的函数,事件监听条件满足后,就执行该函数。函数内部的代码表示,将gold颜色的盒子颜色改为red。综上,以上事件监听完成的功能是:当点击gold颜色的盒子,将其变为red色。
效果展示:
在上图中,点击其它颜色盒子,颜色不会改变,只有被绑定了事件的盒子颜色才会改变。
事件类型
点击事件
click
译成中文是【点击】的意思,它的含义是监听用户鼠标的单击操作,除了【单击】还有【双击】dblclick
<script> // 双击事件类型 btn.addEventListener('dblclick', function () { console.log('等待事件被触发...'); // 改变 p 标签的文字颜色 const text = document.querySelector('.text') text.style.color = 'red' }) </script>
鼠标事件
鼠标事件是指跟鼠标操作相关的事件,如单击、双击、移动等。
mouseenter
监听鼠标是否移入 DOM 元素
mouseleave
监听鼠标是否移出 DOM 元素
键盘事件
keydown
键盘按下触发时,触发事件
keyup
键盘抬起触发时,触发事件
焦点事件
focus
获得焦点时,触发事件
blur
失去焦点时,触发事件
文本框输入事件
input
当用户对文本框输入时,触发事件
事件对象
什么是事件对象
事件对象是事件监听函数自带的对象,其内部存储了事件触发时的相关信息。比如点击事件中,我们可以知道点击的位置;在键盘事件中,我们可以知道按下的是哪一个键位。
获取事件对象
事件对象的获取方式十分简单,事件对象就是调用的函数的第一个参数。
语法:
元素.addEventListener("事件类型", function (e){})
在上述代码中,e
就是我们的事件对象,你可以为这个参数取任何名字,不过我们一般取名为event
,ev
或者e
。
接下来我们观察一下这个参数传进来了什么:
依然是刚才的代码,现在已经触发了事件,我们在控制台中可以看到e
是一个对象,内部有非常多的属性,这些属性就是事件出发时的相关信息。
事件对象常用属性
属性 | 含义 |
type | 触发的事件类型 |
clientX / clientY | 获取光标相对于浏览器左上角的位置 |
offsetX / offsetY | 获取光标相对于当前事件元素左上角的位置 |
key | 用户按下的键盘的值 |
展示一下效果:
事件解绑
既然可以绑定事件,我们也可以在不需要执行相应事件的时候解除绑定。
语法:
解绑对象.removeEventListener("事件类型",函数)
由于一个对象可以绑定多个事件类型,而一个事件也可以绑定多个函数。所以在解绑的时候,一定要指定清楚哪一个对象,哪一个事件,哪一个函数。
以上就是基本语法,我们用一段代码展示效果:
const gold = document.querySelector(".gold"); const skyblue = document.querySelector(".skyblue"); function fn() { alert("正在点击gold盒子"); } gold.addEventListener("click",fn) skyblue.addEventListener("click", function () { gold.removeEventListener("click", fn); alert("gold事件已经解绑"); })
在上述代码中,我们先为gold盒子绑定了一个事件,点击的时候执行fn函数,然后为skyblue盒子绑定事件,当skyblue盒子被点击,就会提示"gold事件已经解绑",并且解绑gold的事件。
效果如下:
原先我们多次点击gold盒子,都会有响应,当我们点击完skyblue盒子后,gold的事件就被解绑了,此后再点击gold盒子,都不会发生事件监听了。
那么我们的代码可以优化成这样吗:
const gold = document.querySelector(".gold"); const skyblue = document.querySelector(".skyblue"); gold.addEventListener("click",function fn() { alert("正在点击gold盒子"); }) skyblue.addEventListener("click", function () { gold.removeEventListener("click", fn); alert("gold事件已经解绑"); })
首先,我们再gold盒子的事件监听中,定义了fn函数,那么fn函数的作用域就在addEventListener
,此时sky blue的事件监听是无法找到fn函数的,系统就会报错:fn is undefined;即没有定义fn函数。所以在确定了要依靠其它函数来解绑事件,那么就要把事件监听的函数写在外面。
此外,调用函数为匿名函数的事件监听无法解绑,因为在解绑的时候,无法找到对应函数名。
环境对象 this
this对象是函数内部的特殊对象,其代表着函数运行时所处的环境。
当不同对象调用同一个函数,此函数的this对象也会不同。
当我们直接调用某一个函数,其this是Windows,即JavaScript中的顶级对象。
我们用案例来说明:
function fn() { console.log(this); } fn(); gold.addEventListener("click", fn) skyblue.addEventListener("click", fn)
fn是一个函数,其执行后,会向控制台输出当前的this对象,代码中共有三处调用了fn。我们看一下三个this的输出结果:
可以看到,不同地方调用同一个函数,其this也会不同。
this对象的指向准则就是:谁调用这个函数,这个函数的this就是谁。
在实际应用中,我们可以用this代指一些复杂的对象名,比如以下代码:
const aVeryVeryVeryVeryVeryVeryVeryLongName = document.querySelector(".gold"); aVeryVeryVeryVeryVeryVeryVeryLongName.addEventListener("click", function () { aVeryVeryVeryVeryVeryVeryVeryLongName.innerHTML = "xxx"; aVeryVeryVeryVeryVeryVeryVeryLongName.style.backgroundColor = "red"; aVeryVeryVeryVeryVeryVeryVeryLongName.style.display = "blocck"; })
如果我们的事件对象名字非常长,比如此处的aVeryVeryVeryVeryVeryVeryVeryLongName
,那么我们后续想要调用这个对象就会很麻烦,而在事件监听函数中,我们的this对象就是aVeryVeryVeryVeryVeryVeryVeryLongName
,所以以上代码可以简化为:
const aVeryVeryVeryVeryVeryVeryVeryLongName = document.querySelector(".gold"); aVeryVeryVeryVeryVeryVeryVeryLongName.addEventListener("click", function () { this.innerHTML = "xxx"; this.style.backgroundColor = "red"; this.style.display = "blocck"; })
事件流
事件流是对事件执行过程的描述,事件流主要分为两个过程:事件捕获与事件冒泡
接下来我们讲解一下两个阶段:
事件捕获
所谓事件捕获,就是在DOM树中找到目标事件的过程。
比如一个外国人想买到正宗的中国陶瓷,那么他就需要“捕获”到陶瓷。这个过程中,需要先在中国寻找江西省,在江西省寻找景德镇,在景德镇寻找陶瓷店铺,最后才能找到陶瓷。这个过程叫做捕获,也就是一层一层往下寻找,直到找到目标为止。
事件冒泡
当寻找到目标后,那么就需要将目标带回,此时陶瓷就会离开陶瓷店,离开景德镇,离开江西省,离开中国,最后才能到达老外手里。这个过程叫做冒泡,即事件捕获后,往外一层一层冒出。
那么我们再解析一下上图:
当我们要寻找一个div,那么就要先事件捕获:进入document,进入html,进入body,找到div。然后再事件冒泡:离开body,离开html,离开document。
事件捕获与事件冒泡的影响
接下来讲解一下事件捕获和事件冒泡在事件监听
中的作用:
现在我们有以下布局的盒子:
对其绑定以下事件:
const skyblue = document.querySelector(".skyblue"); const pink = document.querySelector(".pink"); const gold = document.querySelector(".gold"); const palegreen = document.querySelector(".palegreen"); skyblue.addEventListener("click", function () { console.log("skyblue"); }) pink.addEventListener("click", function () { console.log("pink"); }) gold.addEventListener("click", function () { console.log("gold"); }) palegreen.addEventListener("click", function () { console.log("palegreen"); })
即对每个盒子都绑定了点击事件,点击后会输出当前盒子的名字,接下来我们点击绿色盒子试试:
我们只点击了一个盒子,四个盒子相应的事件全部触发了!
这就是事件冒泡的影响,我们观察一下输出顺序:绿,黄,粉,蓝,刚好就是事件冒泡的顺序。
当一个元素的事件触发时,所有祖先元素绑定的相同类型事件监听都会被触发。
这个地方,四个元素全部绑定了”click“的事件监听,当绿色盒子的”click“事件监听触发时,其所有父级元素的”click“事件监听都会被触发。
而默认的事件监听执行顺序,是在冒泡阶段执行的,这涉及到了addEventListener
的第三个参数,当第三个参数为默认值false时,addEventListener
的函数效果会在冒泡阶段执行;当第三个参数为true时,addEventListener
的函数效果会在捕获阶段执行。
现在我们将所有函数的第三个参数加上truea:
const skyblue = document.querySelector(".skyblue"); const pink = document.querySelector(".pink"); const gold = document.querySelector(".gold"); const palegreen = document.querySelector(".palegreen"); skyblue.addEventListener("click", function () { console.log("skyblue"); },true) pink.addEventListener("click", function () { console.log("pink"); },true) gold.addEventListener("click", function () { console.log("gold"); },true) palegreen.addEventListener("click", function () { console.log("palegreen"); },true)
点击绿色盒子看看效果:
最后我们的执行顺序就是:蓝,粉,黄,绿,即事件捕获的顺序。
毫无疑问,事件监听和事件冒泡对网页交互的负面效果比较多,由于addEventListener
默认在事件冒泡阶段执行,那么我们就需要阻止事件冒泡的发生,这就涉及到阻止冒泡。
而这个也有一些好处,比如事件委托。
接下来我们对两者一一讲解:
阻止冒泡
语法:
事件对象.stopPropagation()
此处的事件对象,就是我们之前讲解的e
,即:e.stopPropagation()
。
这个函数可以阻断事件流动传播,对事件冒泡和事件捕获都有效果。
我们将阻止冒泡加到最里层的绿色盒子中:
const skyblue = document.querySelector(".skyblue"); const pink = document.querySelector(".pink"); const gold = document.querySelector(".gold"); const palegreen = document.querySelector(".palegreen"); skyblue.addEventListener("click", function () { console.log("skyblue"); }) pink.addEventListener("click", function () { console.log("pink"); }) gold.addEventListener("click", function () { console.log("gold"); }) palegreen.addEventListener("click", function (e) { console.log("palegreen"); e.stopPropagation(); })
效果如下:
最后只有绿色盒子执行了事件监听,其它盒子由于事件冒泡被阻断了,没有接收到绿色盒子的冒泡。
事件委托
现在有一个父级盒子ul,和三个子级盒子li:
让三个子级盒子被点击后,都会变成白色,你会怎么做?
基础思路就是,对三个盒子分别”click“进行事件监听,然后将它们的颜色改变为白色。
但是如果你有10个子盒子,20个子盒子,一个一个去监听不经会浪费浏览器的效率,而且代码会很冗余,此时事件委托就出现了。
事件委托利用了事件冒泡的特性,当我们点击三个子级的盒子,父级也会接收到”click“事件的效果。那么此时我们只要监听父级盒子,当子级盒子被点击后,”click“冒泡到父级盒子,父级盒子的事件监听就触发了。
那么当父级盒子事件监听触发后,要如何让子级元素产生效果?
在事件对象e中,有一个属性值target,它存储着此次事件触发的元素。
我们尝试执行以下代码:
const skyblue = document.querySelector(".skyblue"); const pink = document.querySelector(".pink"); const gold = document.querySelector(".gold"); const palegreen = document.querySelector(".palegreen"); skyblue.addEventListener("click", function (e) { console.log(e.target); })
每次点击时,输出当前的e.target
:
可以看到,我们点击哪一个盒子,e.target
就是谁。
此外e.target.tagName
存储着触发元素的元素名,不过是大写的:
由于我们的父子级元素的标签类型不同,可以使用这种方式来区分我们点击了子级盒子还是父级盒子:
skyblue.addEventListener("click", function (e) { if (e.target.tagName === "LI") { e.target.style.backgroundColor = "#ffffff"; } })
这样只有点击的盒子是LI时,才能触发变白的效果,最后我们看看结果:
点击小盒子的时候,会变为白色,点击大盒子则不会。
这就是事件委托:将多个子级的事件委托给父级,利用冒泡特性让父级触发效果,再用e.target
找到触发对象,来执行效果。
事件委托可以有效减少代码量,和事件监听的数目。