这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战
大家好,我是一碗周,一个不想被喝(内卷)的前端。如果写的文章有幸可以得到你的青睐,万分有幸~
这是【从头学前端】系列文章的第四十四篇-《原生DOM中的事件》
本系列文章在掘金首发,编写不易转载请获得允许
写在前面
事件是什么
所谓事件,就是浏览器告知JavaScript程序用户的行为,并且会提供一个自动加载某种动作(例如:运行一些代码)的机制。例如:如果用户在网页上点击一个按钮,显示一个信息框来响应这个动作。
在HTML中,时间在浏览器窗口中被触发并且通常被绑定到窗口内部的待定部分——可能是一个元素、一系列元素、被加载到这个窗口的HTML代码或者整个浏览器窗口。
每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器。注意事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。
我们常见的事件类型都是DOM预定义好的,也就是说我们可以直接使用的。
事件类型
根据事件的使用场景不同,我们人为的可以将事件分为如下几种
- 依赖于设备的输入事件:键盘事件和鼠标事件,这些事件都是直接和设备相关的。
- 独立于设备的输入事件:例如
click
事件等,这些事件并没有直接与设备相关。 - 用户界面的相关事件:用户界面事件属于比较高级的事件,一般多用于表单中的组件。
- 状态变化的相关事件:这些事件与用户行为无关,而是有网络或浏览器触发的。
- 特定API事件:这些事件多用于特定场景的事件,例如HTML5中提供的拖放API中的事件等。
- 错误处理的相关事件:一般都是与错误处理有关的事件。
事件的实现步骤
我们事件的实现步骤如下:
- 定位页面中指定的元素
- 为指定元素绑定事件,具有一个事件监听器(监听用户相应的行为)
- 为绑定的事件编写相应的处理函数(用户触发相应的行为时,函数做出的响应)
示例代码如下所示:
<body>
<button id="btn">按钮</button>
<script>
// 1. 定位页面中指定的元素
var btn = document.getElementById('btn')
// 2. 为指定元素绑定事件 - 具有一个事件监听器(监听用户相应的行为)
btn.onclick = function () {
// 3. 为绑定的事件编写相应的处理函数(用户触发相应的行为时,函数做出的响应)
console.log("我被点击了")
}
</script>
</body>
复制代码
注册事件
所谓主词事件,就是将JavaScript函数与指定的事件相关联,被绑定的函数成为该事件的句柄。当该事件被触发时,绑定的函数会被调用。
注册事件具有以下三种方式实现:
- HTML页面元素提供的事件属性
- DOM标准规范中HTML相关对象提供的事件属性
- 通过向HTML页面中指定元素添加事件监听器
下面我们就分别说说这三种事件。
HTML页面元素提供的事件属性
HTML页面元素提供的事件属性是元素分类的一种,DOM提供了事件的名称。示例代码如下所示:
<body>
<button onclick="myClick()"
id="btn">按钮</button>
<script>
function myClick() {
console.log('你终于点中了我...')
}
</script>
</body>
复制代码
当点击点击页面的按钮后,在console面板就会打印你终于点中了我...
DOM对象提供的事件属性
通过DOM标准规范中的Document对象定位HTML页面的元素,所返回的D0M对象提供了一系列的事件属性,通过这些事件属性可以实现注册事件的功能。示例代码如下:
<body>
<button id="btn">按钮</button>
<script>
var btn = document.getElementById('btn')
// DOM对象的事件属性
btn.onclick = myClick;
function myClick() {
console.log('你终于点中了我...')
}
</script>
</body>
复制代码
事件监听器
DOM 标准规范中提供了EventTarget.addEventListener()
方法,调用该方法表示想指定元素添加事件监视器。语法结构如下
element.addEventListener(eventName, functionName, capture)
复制代码
参数说明
evenName
:为元素指定具体的事件名称(注意,没有on)functionName
:表示事件的处理函数capture
:可选,是否阻止事件冒泡,默认值为false
示例代码如下
<body>
<button id="btn">按钮</button>
<script>
var btn = document.getElementById('btn')
// 事件监听器
/*
element.addEventListener(eventName, functionName)
* 参数
* eventName:绑定事件的事件名称(注意,没有on)
* functionName:表示事件的处理函数
*/
btn.addEventListener('click', myClick)
function myClick() {
console.log('你终于点中了我...')
}
</script>
</body>
复制代码
使用该方法注册事件的时候,使用this
就指代当前注册事件的元素。
事件监听器与DOM对象提供的事件属性的区别
事件监听器 与DOM对象提供的事件属性都做到了结构与行为相分离,但是两者是存在着区别的,示例代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible"
content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>注册事件的区别</title>
</head>
<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
// 使用事件监听器为按钮1新增两个事件
btn1.addEventListener('click', myClick1)
// 使用DOM对象的事件属性为按钮2绑定两个事件
btn2.onclick = myClick1
btn2.onclick = myClick2
// 执行函数
function myClick1() {
console.log('你终于点中了我...')
}
btn1.addEventListener('click', myClick2)
function myClick2() {
console.log('你终于又点中了我...')
}
</script>
</body>
</html>
复制代码
我们可以看出,使用事件监听器可以绑定多个事件,但是使用 DOM 对象提供的事件属性只可以添加一个(添加第二个时是重新赋值),所以我们一般在实际开发中使用事件监听器的方式。
移除注册事件
EventTarget.removeEventListener()
方法删除使用EventTarget.addEventListener()
方法添加的事件。
语法结构如下所示:
element.removeEventListener(eventName, functionName, capture)
复制代码
参数说明
evenName
:为元素指定具体移除的事件名称(注意,没有on)functionName
:表示要移除事件中的某个的处理函数,capture
:可选,是否阻止事件冒泡,默认值为false
示例代码如下:
var btn = document.getElementById('btn')
// 使用事件监听器为按钮新增两个事件
btn.addEventListener('click', myClick)
// 执行函数
function myClick() {
console.log('你终于点中了我...')
}
// 移除 myClick2 这个执行函数
btn.removeEventListener('click', myClick)
复制代码
Event接口
Event
接口表示在DOM中发生的任何事件; 一些是用户生成的(例如鼠标或键盘事件),而其他由 API 生成(例如指示动画已经完成运行的事件,视频已被暂停等等)。
事件处理函数可以附加在各种对象上,包括DOM元素,window对象等。当事件发生时, event
对象就会被创建并依次传递给事件监听器。
在处理函数中,将event
对象作为第一个参数参数,可以访问DOM Event接口。如下代码展示event
对象如何使用:
<body>
<button id="btn">按钮</button>
<script>
var btn = document.getElementById('btn')
// event 作为第一个参数出现
btn.addEventListener('click', function (event) {
console.log(event)
})
</script>
</body>
复制代码
阻止默认行为
默认行为是什么
所谓默认行为,就是指HTML元素不借助JavaScript逻辑原本具有的动态效果。例如以下HTML元素:
<a>
元素的跳转功能<form>
元素中点击<input type="submit">
提交按钮是,提交表单的功能。- 输入框的输入文本内容。
- 单选框或者复选框的切换选择功能。
preventDefault()方法
Event事件对象提供了preventDefault()
方法,用于阻止浏览器的默认行为。语法结构如下:
event.preventDefault();
复制代码
示例代码如下:
<body>
<a href="www.baidu.com">打开百度</a>
<div id="output"></div>
<script>
// 定位a元素
var a = document.getElementsByTagName('a')[0]
// 为a绑定点击事件
a.addEventListener('click', function (event) {
event.preventDefault()
var output = document.getElementById('output');
output.innerHTML += "对不起,<code> event.preventDefault() </code>方法使得 a 元素不可用<br>"
})
</script>
</body>
复制代码
事件流
事件流是什么
所谓事件流,就是当触发某个元素的事件时,事件会按照DOM结构树进行传播,传播的过程如下:
- 捕获阶段:该阶段是由网景公司提出的。按照DOM结构由
document
对象向下的顺序传播,直到目标元素为止。 - 目标阶段:该阶段就是值目标元素触发当前事件
- 冒泡阶段:该阶段是由微软公司提出的,按照DOM结构树由目标元素向上的顺序传播,直到
document
对象
触发事件流的条件如下所示:
- 这些元素之间的关系是祖先与后代的关系
- 这些元素绑定相同的事件
事件的三个阶段
现在我们通过这段代码来看一下事件的这三个阶段,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible"
content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>事件流</title>
<style>
* {
margin: 0;
box-sizing: border-box;
}
#main {
height: 300px;
width: 300px;
background-color: lightsalmon;
padding: 50px;
margin: 100px;
}
#div {
height: 200px;
width: 200px;
background-color: lightskyblue;
padding: 50px;
}
#p {
height: 100px;
width: 100px;
background-color: lightyellow;
}
</style>
</head>
<body>
<main id="main">
<div id="div">
<p id="p"></p>
</div>
</main>
<script>
var main = document.getElementById('main')
var div = document.getElementById('div')
var p = document.getElementById('p')
// 绑定相同事件
main.addEventListener('click', function () {
console.log('我是main标签')
}, false)
div.addEventListener('click', function () {
console.log('我是div标签')
}, false)
p.addEventListener('click', function () {
console.log('我是p标签')
}, false)
</script>
</body>
</html>
复制代码
该代码的执行结果如下:
我们可以将这个过程画出如下过程图:
当我们点击<p>
这个块的时候,事件会从Window对象开始逐层传递,直到<p>
触发click
事件,然后执行我们的回调函数;然后执行完毕之后会逐层向上冒泡,然后就会执行为<div>
和<main>
绑定的事件处理函数。
取消事件冒泡
取消事件冒泡有两种方式,一种是通过addEventListener()
方法注册事件时,将第三个参数设置为true
;另一种就是通过event
对象提供的stopPropagation()
方法。
事件委托
当为大量的HTML元素注册相同事件,并且事件的句柄逻辑完全相同,会造成页面速度下降。不过,事件流允许这些HTML元素的共同父级注册事件。这种方式称为事件委托。
我们先看一段代码
<body>
<div id="container">
<button id="btn1">按钮</button>
<button id="btn2">按钮</button>
<button id="btn3">按钮</button>
</div>
<script>
var btn1 = document.getElementById('btn1')
btn1.addEventListener('click', function () {
console.log('我是按钮')
})
var btn2 = document.getElementById('btn2')
btn2.addEventListener('click', function () {
console.log('我是按钮')
})
var btn3 = document.getElementById('btn3')
btn3.addEventListener('click', function () {
console.log('我是按钮')
})
</script>
</body>
复制代码
现在我们通过事件委托来改写这个代码,代码如下
var container = document.getElementById('container')
container.addEventListener('click', function (event) {
var target = event.target
// 当前点击的为 button 时,才会触发下面逻辑代码
if (target.nodeName == "BUTTON") {
console.log('我是按钮')
}
})
复制代码
我们可以明显看到,代码量的节省,但是最终的运行结果是一样的。
写在最后
你如果看到这里,我感到很荣幸,如果你喜欢这篇文章,你可以为这篇文章点上一个小赞;你如果喜欢这个专栏,我会一直更新到百篇以上,可以点一下后面的链接从头学前端 - 一碗周的专栏 - 掘金 (juejin.cn)进入之后给个关注。
最后也可以给我点个关注,万分荣庆。
往期推荐