快!给!我!来!学!浏览器事件!

147 阅读7分钟

事件

观察者模式

事件流描述了页面接收事件的顺序。

事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播到没有那么具体的元素(window)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>
      <div>
        <div class="box">测试</div>
      </div>
    </div>
  </body>
  <script>
    const box = document.querySelector(".box");
    box.addEventListener("click", () => {
      console.log("box");
    });
    document.addEventListener("click", () => {
      console.log("document");
    });
    window.addEventListener("click", () => {
      console.log("window");
    });
  </script>
</html>

可以看到,事件冒泡到了最上层window

事件捕获:从最外层触发事件,逐渐到最内层。 可以进行一些事件的拦截。

DOM0事件处理程序

把一个函数赋值给一个事件处理程序属性。

const box = document.querySelector(".box");
    box.onclick = function () {
      console.log("this==", this);
    };

这里要注意,如果使用function字面量定义函数,则this是dom元素本身

如果用箭头函数,由于箭头函数本身没有this,this为window。

移除事件处理程序

const box = null

DOM事件处理程序

主要是两个方法: addEventListener() 和 removeEventListener

接收三个参数

  1. 事件名
  2. 事件处理函数
  3. options
    1. options
      1. capture: 捕获阶段传播到eventTarget触发
      2. once: 最多调用一次,调用完自动移除
      3. passive:永远不会调用preventDefault,如果调用了,会报错
      4. signal: AbortSignal 的abort方法被调用时,监听器被移除。
  1. useCapture: 控制捕获阶段处理还是冒泡阶段处理。

添加一个监听器

const box = document.querySelector(".box");
   
box.addEventListener("click", () => {
  console.log("box");
});

移除监听器

方案1

兼容性不太好

// 为 table 添加可被移除的事件监听器
const controller = new AbortController();
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, { signal: controller.signal });

// 改变 t2 内容的函数
function modifyText() {
  const t2 = document.getElementById("t2");
  if (t2.firstChild.nodeValue === "three") {
    t2.firstChild.nodeValue = "two";
  } else {
    t2.firstChild.nodeValue = "three";
    controller.abort(); // 当值变为 "three" 后,移除监听器
  }
}

方案2

    const box = document.querySelector(".box");
    const handleClick = ()=>{}
    box.addEventListener("click", handleClick);
    box.removeEventListener("click",handleClick)

基于这个方法可以监听多个事件,多个事件处理程序按照添加顺序来触发。

事件对象

在dom发生事件时,所有相关信息都会被收集并存储在一个event对象中。

长这样

事件类型

用户界面事件

涉及与BOM交互的通用浏览器事件

  • DOMActivate: 元素被用户通过鼠标或键盘操作时激活,已经被废弃
  • load: 在window上当页面加载完成后触发,在 上当所有窗格都加载完成后触发,在img元素上当图片加载完成后触发。
  • unload:当页面完全卸载后触发
  • abort: 被终止触发
  • error:js报错触发,img无法加载图片触发。
  • resize: 窗口变化触发
  • scroll: 滚动触发

焦点事件(只写我觉得重要的把。。。 )

  • blue: 失去焦点触发。不冒泡,所有浏览器支持。
  • fouce: 获取焦点触发。不冒泡
  • foucein:获得焦点触发,冒泡版本
  • focusout:失去焦点触发,通用版

鼠标和滚轮事件

  • click: 单机鼠标左键或按回车触发(无障碍)。
  • dbclick:双击触发
  • mousedown: 用户按下任意鼠标键时出发。
  • mouseenter: 鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,不会再光标经过后代元素时触发。
  • mouseleave: 用户把鼠标光标从元素内部移到元素外部触发。这个事件不冒泡,也不会在光标经过后代元素时触发。
  • mousemove:鼠标光标在元素上移动时反复触发。
  • mouseout: 从一个元素移动到另一个元素上触发。移动元素可以是原始元素的外部元素,也可以是原始元素的子元素
  • mouseover: 用户把鼠标光标从元素外部移到元素内部触发。
  • mouseup: 用户释放鼠标键触发。
触发顺序
  1. mousedown
  2. mouseup
  3. click
  4. mousedown
  5. mouseup
  6. click
  7. dpclick

键盘与输入事件

  • keydown: 用户按下键盘上某个键触发,持续按住会重复触发。
  • keypress:用户按下键盘某个键并产生字符触发,持续按住会重复触发。 这个特性已经废弃,可以使用beforeinput
  • keyup:用户释放键盘上某个键位触发。

HTML5事件

  1. contextmenu 事件: 上下文菜单概念,使用之前要先取消默认事件。
  2. beforeunload: 提供页面被卸载的机会。 会组织浏览器往返缓存。
  3. DOMContentLoaded :当 HTML 文档完全解析,且所有延迟脚本(
  4. readystatechange: 当文档的 readyState 属性发生改变时,会触发 readystatechange 事件。

DOMContentLoaded 不会等待样式表加载,但延迟脚本会等待样式表,而且 DOMContentLoaded 事件排在延迟脚本之后。此外,非延迟或异步的脚本(如

  1. pageshow:页面显示时触发。
  2. pagehide:页面隐藏时触发。
  3. hashchange:URL散列值发生变化时通知开发者。

设备事件

  • orientationchange: orientationchange事件在设备的纵横方向改变时触发。
  • deviceorientation : 事件在方向传感器输出新数据的时候触发。其数据系传感器与地球坐标系相比较所得,也就是说在设备上可能会采用设备地磁计的数据。

触摸及手势事件

  • touchstart: 事件在一个或多个触点与触控设备表面接触时被触发。
  • touchmove: 事件在触点于触控平面上移动时触发。
  • touchend: 事件在一个或多个触点从触控平面上移开时触发。注意,也有可能触发 touchcancel 事件。

1. 事件触发逻辑上的区别

特性mouseentermouseover
冒泡不会冒泡会冒泡
触发频率只在鼠标首次进入目标元素时触发鼠标进入目标元素及其子元素时都会触发
适合的场景希望处理进入目标元素的事件(不包含子元素)需要监听鼠标进入目标和子元素的所有事件

示例 1: mouseenter 不会冒泡

javascript


复制代码
const parent = document.getElementById('parent');
const child = document.getElementById('child');

parent.addEventListener('mouseenter', () => {
  console.log('mouseenter: Parent element');
});

child.addEventListener('mouseenter', () => {
  console.log('mouseenter: Child element');
});
效果
  • 当鼠标进入 child 时,只触发 childmouseenter 事件,不会触发 parentmouseenter 事件。
  • 这是因为 mouseenter不会冒泡,即鼠标进入子元素时,不会触发父元素的事件。

示例 2: mouseover 会冒泡

javascript


复制代码
const parent = document.getElementById('parent');
const child = document.getElementById('child');

parent.addEventListener('mouseover', () => {
  console.log('mouseover: Parent element');
});

child.addEventListener('mouseover', () => {
  console.log('mouseover: Child element');
});
效果
  • 当鼠标进入 child 时,会先触发 childmouseover 事件,然后冒泡到 parent,触发 parentmouseover 事件。
  • 这表明 mouseover 事件会从子元素冒泡到父元素。

2. 触发频率的区别

  • mouseenter:只在鼠标首次进入目标元素时触发,即使鼠标在元素内的子元素之间移动,也不会再次触发。
  • mouseover:当鼠标进入目标元素及其子元素时,都会触发多次。

示例 3: mouseenter vs mouseover 的触发频率

javascript


复制代码
const parent = document.getElementById('parent');

parent.addEventListener('mouseenter', () => {
  console.log('mouseenter: Entered parent');
});

parent.addEventListener('mouseover', () => {
  console.log('mouseover: Entered parent or child');
});
效果
  1. 当鼠标进入 parent 时,mouseentermouseover 都会触发一次。
  2. 如果鼠标在 parent 内的子元素之间移动:
    • mouseenter 不会再触发。
    • mouseover 会在鼠标每次进入子元素时再次触发。

3. 总结:何时使用?

  • mouseenter:适用于希望在鼠标进入特定元素时触发事件,而忽略子元素的进入。
    • 例子:在鼠标悬停于菜单项时高亮显示整个菜单。
  • mouseover:适用于需要监听鼠标进入元素及其子元素的所有事件。
    • 例子:在鼠标悬停于某区域时,跟踪其路径或显示子元素的交互效果。

4. 图示帮助理解

  1. mouseenter
csharp


复制代码
[Parent Element] <- Mouse enters here: Triggered once
    [Child Element] <- Mouse moves here: No new event triggered

2. mouseover

arduino


复制代码
[Parent Element] <- Mouse enters: Triggered
    [Child Element] <- Mouse enters: Triggered again (and bubbles to parent)

事件优化

事件委托

事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。

删除事件处理程序

应该及时删除事件处理程序,否则会占用大量的内存。

模拟事件

最近应该用不上,后续再补充把~