JavaScript:DOM事件


事件监听

什么是事件监听

事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。

例如:用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图

其中,【鼠标点击】和【鼠标拖拽】就是事件,那么这些事情发生之后,为了让JavaScript可以知道,于是就有了事件监听。


事件监听的方式

addEventListener 是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。

语法:

元素对象.addEventListener("事件类型", 要执行的函数)
  • 元素对象:检测谁被执行了事件,只有指定的元素对象执行了指定的事件,事件监听才会执行
  • 事件类型:用在网页中的操作有非常多种,事件类型用于指定只有执行了哪一个事件,事件监听才会被触发
  • 要执行的函数:当事件监听检测到目标事件发生,那么就需要做出相应的反应,此处的反应,就是执行这个函数

完成事件监听分成以下步骤:

  1. 获取 DOM 对象
  2. 通过 addEventListener 方法为 DOM 对象添加事件监听
  3. 等待事件触发
  4. 事件触发后,执行相对应的函数

示例:

 <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就是事件对象,你可以为这个参数取任何名字,不过一般取名为eventev或者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内部,此时skyblue的事件监听是无法找到fn函数的,系统就会报错:fn is undefined;即没有定义fn函数。所以在确定了要依靠其它函数来解绑事件,那么就要把事件监听的函数写在外面

此外,调用函数为匿名函数的事件监听无法解绑,因为在解绑的时候,无法找到对应函数名。


环境对象 this

this对象是函数内部的特殊对象,其代表着函数运行时所处的环境。

当不同对象调用同一个函数,此函数的this对象也会不同。

当直接调用某一个函数,其thisWindows,即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的函数效果会在捕获阶段执行

现在将所有函数的第三个参数加上true

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(); // 阻止冒泡
})

效果如下:

在这里插入图片描述

最后只有绿色盒子执行了事件监听,其它盒子由于事件冒泡被阻断了,没有接收到绿色盒子的冒泡。当时间冒泡触发时,被绿色盒子的stopPropagation截断了,就不再往上冒泡,触发祖先的函数了。


事件委托

现在有一个父级盒子ul,三个子级盒子li

在这里插入图片描述

要让三个子级盒子被点击后,都会变成白色,应该怎么做?

基础思路就是,对三个盒子分别”click“进行事件监听,然后将它们的颜色改变为白色。
但是如果你有10个子盒子,20个子盒子,一个一个去监听不仅会降低浏览器的效率,而且代码会很冗余,此时事件委托就发挥作用。

事件委托利用了事件冒泡的特性,当点击三个子级的盒子,父级也会接收到click事件的效果。那么此时只要监听父级盒子,当子级盒子被点击后,click冒泡到父级盒子,父级盒子的事件监听就触发了

那么当父级盒子事件监听触发后,要如何让子级元素产生效果?

在事件对象e中,有一个属性值arget,它存储着此次事件触发的元素。

示例:

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找到触发对象,来执行效果。事件委托可以有效减少代码量,和事件监听的数目。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盒马盒马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值