Skip to content

事件处理和事件循环

发布日期:2021-03-08

本文总结了浏览器事件的冒泡阶段和捕获阶段,及其事件对应的两个target的差异;总结了事件循环、宏任务、微任务的概念。


事件模型

原始事件模型(DOM0级)

该事件模型有两种绑定方式,是最原始的事件绑定方式,梦回前端原始人:

  1. 直接在 html 中绑定事件
<input id="myButton" type="button" value="Press Me" onclick="alert('thanks');" >
  1. 在 js 中为 dom 绑定事件
// 绑定事件
document.getElementById("myButton").onclick = function () {alert('thanks');}
// 取消事件绑定
document.getElementById("myButton").onclick = null

特点:

  1. DOM0事件只支持冒泡,不支持捕获
  2. 同一类型事件只能绑定一个

标准事件模型(DOM2级)

最常用的事件绑定方式,事件触发有三个阶段:捕获阶段、事件处理阶段、冒泡阶段

// 可以添加第三个参数 useCapture: boolean 表示在捕获阶段触发事件
document.getElementById('myButton').addEventListener('click', function(){alert('hello')})
// 可以通过 removeEventListener 方法来取消事件绑定

特点:

  1. 一个元素可以绑定多个同类型事件
  2. 有多个事件触发阶段,对于复杂嵌套元素的事件触发顺序很重要

IE事件模型

只有两个阶段:事件处理阶段、冒泡阶段。事件绑定采用 attachEventdetachEvent 方法,只在 IE 浏览器生效。

事件的冒泡阶段和捕获阶段

js事件有两个阶段,分别是冒泡阶段和捕获阶段

点击一个元素时,事件将从最外层元素开始向最内层元素传播,这个阶段叫 捕获阶段

事件 => | => 外层元素 | => 内层元素

到达最内层元素之后,事件将从内层元素向外层元素传播,这个阶段叫 冒泡阶段,是默认的事件触发方式

<= | 外层元素 <= | 内层元素 <= 事件

target 和 currentTarget

事件触发时的 event 对象有两个 “target”,target 是触发事件的元素,currentTarget 是绑定事件的元素,在为元素绑定 dataset 时要注意区分到底要绑定在哪里,以及在哪里取这个数据。

比如我们为父元素绑定了事件,此时点击子元素触发事件。currentTarget 就是父元素,target 就是子元素。

爷元素父元素(绑定事件元素)子元素(触发事件元素)
-currentTargettarget

event 对象的几个重要方法

  1. preventDefault() 阻止默认事件,比如 a 标签的默认事件是跳转
  2. stopPropagation() 阻止事件继续冒泡
  3. stopImmediatePropagation()阻止监听同一事件的其他事件监听器被调用。如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用

事件循环

Javascript 是单线程的,有一个 main thread 主线程和一个 call-stack 调用栈(执行栈)。

事件循环开始时,先执行同步代码,再检查微任务队列,执行所有的微任务,再从任务队列中取下一个宏任务,进入下一个事件循环

关于调用栈

  1. 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
  2. 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
  3. 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
  4. 当分配的调用栈空间被占满时,会引发“堆栈溢出”。

https://www.cnblogs.com/shuajing/p/10800656.html

宏任务 Macio Task

包括 script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

定时器 setTimeout:定时器会在等待了指定时间之后将事件放入任务队列中,但必须等到同步任务和任务队列中的事件全部执行完成后,才会被执行,因此定时器并不一定准时。

微任务 Micro Task

包括 process.nextTick(nodejs), Promise(的回调), Object.observe, MutationObserver

process.nextTick 只在 node 中存在,递归调用可以使得事件循环停止

https://www.jianshu.com/p/5328c72279ff

Object.observe 已被废弃的接口

MutationObserver 接口提供了监视对DOM树所做更改的能力

https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver

如何在浏览器中使得事件循环永远到达不了下一个?使用 Promise 不断添加微任务,只要不完成微任务,就不能开始下一个事件循环。使用这种方式不会导致栈溢出。

javascript
function loop () {
  console.log(1)
  return new Promise(resolve => {
    resolve()
  })
}
var i = 0
let target = loop()
while (i < 1000) {
  i++
  target.then(loop)
}
console.log(2)
setTimeout(() => {
  console.log(3)
}, 0)

// console: 
// 1
// 2
// (1000)1
// 3

关于 requestAnimationFrame 不应该被归类在宏任务和微任务中,它和浏览器刷新率有关。

https://juejin.cn/post/7134972903816167455

Power by vitepress