JS Advance --- 事件处理

455 阅读4分钟

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

为了实现JavaScript脚本和浏览器之间的交互,浏览器给我们提供的BOM、DOM等一些对象模型

事实上还有一种需要和浏览器经常交互的事情就是事件监听

浏览器在某个时刻可能会发生一些事件,比如鼠标点击、移动、滚动、获取、失去焦点、输入内容等等一系列 的事件

我们需要以某种方式(代码)来对其进行响应,进行一些事件的处理

在Web当中,事件在浏览器窗口中被触发,并且通过绑定到某些元素上或者浏览器窗口本身,那么我们就可以给这些元素或者window窗口来绑定事件的处理程序,来对事件进行监听

监听方式

在script中直接监听

<!--
  我们可以将对于的事件响应逻辑写在行内
  也可以单独抽离出现形成一个事件处理函数
-->
<div id="box" onclick="console.log('box被点击了')"></div>

通过元素的on来监听事件

使用这种添加方式,其实和在script中直接监听的本质区别不大

只不过使用on来监听事件,是获取元素后,在js代码中添加事件

而不是在html中对元素添加对应的事件

但他们本质上都是对元素的onXXX属性进行赋值

所以后一个事件处理函数会将前一个事件处理函数覆盖

只能绑定一个事件处理函数

const el = document.getElementById('box')
el.onclick = () => console.log('box被点击了')

通过EventTarget中的addEventListener来监听

使用addEventListener方式来添加事件监听函数是为了解决使用onXXX方式添加对应的事件处理函数的时候

只能添加一个事件响应函数这个问题而提出的新api

使用addEventListener方法可以给元素添加多个事件处理函数

多个事件处理函数之间被不冲突,包括使用onXXX方法添加的事件处理函数

const el = document.getElementById('box')

el.onclick = () => console.log('box被点击了1') // 被触发
el.addEventListener('click', () => console.log('box被点击了2')) // 被触发
el.addEventListener('click', () => console.log('box被点击了3')) // 被触发

事件流

我们在浏览器上对着一个元素点击时,你点击的不仅仅是这个元素本身

元素的父容器也会被点击,父容器的父容器也会被点击,... 一直到body元素

这是因为我们的HTML元素是存在父子元素叠加层级的

而这种事件触发的方式就被称之为事件流

因为早期浏览器开发时,不管是IE还是Netscape公司都发现了这个问题,但是他们采用了完全相反的事件流来对事件进行了传递

IE采用了内元素事件先被触发,再触发外层元素事件(事件冒泡 Event Bubble)的方式

而Netscape采用了外层元素先被触发,内层元素事件再被触发(事件捕获 Event Capture)的方式;

// addEventListener第三个参数就是用来控制对应的事件是使用冒泡的方式被触发,而是使用捕获的方式被触发
// 默认false --- 使用冒泡方式被触发
// 设置为true的时候,使用捕获的方式被触发
el.addEventListener('click', () => console.log('box被点击了2'), true)

IxtVYr.png

如果我们同时有事件冒泡和时间捕获的监听,那么会优先监听到事件捕获,再处理事件冒泡

事件对象

当一个事件发生时,就会有和这个事件相关的很多信息

比如事件的类型是什么,你点击的是哪一个元素,点击的位置是哪里等等相关的信息

这些信息会被封装到一个Event对象中,并作为事件处理函数的参数进行传递

该对象给我们提供了想要的一些属性,以及可以通过该对象进行某些操作

// 获取事件对象
el.addEventListener('click', e => console.log(e))
const el = document.getElementById('box')

el.addEventListener('click', e => {
  console.log(e.type) // => click  事件类型
  console.log(e.target) // => el元素 实际触发事件的那个元素
  // 在实际触发对应事件的元素的事件响应函数中
  // e.target和e.currentTarget是同一个对象
  console.log(e.currentTarget) // => el元素 真正处理事件的元素
  console.log(e.offsetX, e.offsetY) // 元素被点击的位置
})

document.body.addEventListener('click', e => {
  console.log(e.target) // el元素
  console.log(e.currentTarget) // body元素
})
const el = document.getElementById('box')

el.addEventListener('click', e => {
  // 阻止事件的进一步传递
  // 包括事件冒泡和事件捕获都会被阻止
  e.stopPropagation()
})
const el = document.getElementById('box')

el.addEventListener('click', e => {
  // 取消事件的默认行为
  e.preventDefault()
})

stopImmediatePropagation vs stopPropagation

const el = document.getElementById('box')

el.addEventListener('click', e => {
  // 如果一个元素上被绑定了多个事件响应函数
  // 使用stopImmediatePropagation会阻止除当前事件处理函数之外的全部事件处理函数
  // 包括绑定在同一个元素上的其它事件处理函数
  e.stopImmediatePropagation()
  console.log(111) // 触发
})

el.addEventListener('click', e => {
  console.log(222) // 没有触发
})

el.addEventListener('click', e => {
  console.log(333) // 没有触发
})
const el = document.getElementById('box')

el.addEventListener('click', e => {
  // 使用Propagation会阻止除当前事件处理函数之外的全部事件处理函数
  // 如果一个元素上被绑定了多个事件响应函数
  // 这些事件处理函数都会被触发
  e.stopPropagation()
  console.log(111) // 触发
})

el.addEventListener('click', e => {
  console.log(222) // 触发
})

el.addEventListener('click', e => {
  console.log(333) // 触发
})