编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

    • 基础入门

      • README
      • 入门介绍
      • 数据类型
      • 运算符
      • 函数
      • 面向对象
      • 标准库
      • 异步操作
      • 事件设计
        • 8.1 基本概念
          • 8.1.1 基本概念
          • 8.1.2 事件接口方法
          • 8.1.3 事件驱动的设计原理
        • 8.2 事件监听
          • 8.2.1 addEventListener
          • 8.2.2 使用HTML属性
          • 8.2.3 使用DOM属性
          • 8.2.4 removeEventListener
          • 8.2.5 dispatchEvent
          • 8.2.6 addEventListener高级选项
        • 8.3 事件对象
          • 8.3.1 事件对象常用属性
        • 8.4 事件传播
          • 8.4.1 捕获阶段
          • 8.4.2 冒泡阶段
          • 8.4.3 阻止事件传播
          • 8.4.4 事件传播的底层原理
        • 8.5 事件委托
          • 8.5.1 事件委托的原理与实现
          • 8.5.2 通用事件委托函数
        • 8.6 自定义事件
          • 8.6.1 CustomEvent详解
          • 8.6.2 非DOM的事件系统
        • 8.7 事件类型
          • 8.7.1 常见DOM事件
          • 8.7.2 最佳实践
          • 8.7.3 表单验证
          • 8.7.4 鼠标和键盘事件详解
      • 错误机制
      • 模块开发
      • 字符串处理
      • 迭代器与生成器
      • Symbol
      • DOM操作
      • 网络请求
    • 综合案例

    • 专栏博客

  • CodeX
  • JavaScript入门
  • 基础入门
杨充
2025-06-24
目录

事件设计

# 事件设计

# 目录介绍

  • 8.1 事件基本概念
    • 8.1.1 基本概念
    • 8.1.2 事件接口方法
    • 8.1.3 事件驱动的设计原理
  • 8.2 事件监听
    • 8.2.1 addEventListener
    • 8.2.2 使用HTML属性
    • 8.2.3 使用DOM属性
    • 8.2.4 removeEventListener
    • 8.2.5 dispatchEvent
    • 8.2.6 addEventListener高级选项
  • 8.3 事件对象
    • 8.3.1 事件对象常用属性
  • 8.4 事件传播
    • 8.4.1 捕获阶段
    • 8.4.2 冒泡阶段
    • 8.4.3 阻止事件传播
    • 8.4.4 事件传播的底层原理
  • 8.5 事件委托
    • 8.5.1 事件委托的原理与实现
    • 8.5.2 通用事件委托函数
  • 8.6 自定义事件
    • 8.6.1 CustomEvent详解
    • 8.6.2 非DOM的事件系统
  • 8.7 事件类型
    • 8.7.1 常见DOM事件
    • 8.7.2 最佳实践
    • 8.7.3 表单验证
    • 8.7.4 鼠标和键盘事件详解

# 8.1 基本概念

JavaScript 事件设计是前端开发中的核心部分,它允许开发者对用户交互(如点击、输入、滚动等)和浏览器行为(如加载、卸载等)做出响应。

事件系统的底层原理:浏览器的事件系统基于观察者模式(Observer Pattern) 实现。当用户触发一个事件(如点击),浏览器会创建一个事件对象(Event Object),然后按照 W3C 标准的三阶段模型分发事件:捕获阶段(从 window → document → ... → 目标元素的父节点)→ 目标阶段(到达目标元素)→ 冒泡阶段(从目标元素 → ... → document → window)。事件对象在整个传播过程中是同一个实例,这就是为什么 event.target 始终指向触发事件的原始元素,而 event.currentTarget 指向当前正在处理事件的元素。

# 8.1.1 基本概念

  • 事件:用户或浏览器触发的动作,如点击、鼠标移动、键盘输入等。
  • 事件目标(Event Target):触发事件的 DOM 元素。
  • 事件监听器(Event Listener):用于监听事件并执行回调函数。
  • 事件传播(Event Propagation):事件从目标元素向上或向下传播的过程,包括捕获阶段和冒泡阶段。

# 8.1.2 事件接口方法

DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest、AudioNode、AudioContext)也部署了这个接口。

该接口主要提供三个实例方法。

  • addEventListener():绑定事件的监听函数
  • removeEventListener():移除事件的监听函数
  • dispatchEvent():触发事件

# 8.1.3 事件驱动的设计原理

疑惑:为什么浏览器要采用事件驱动模型,而不是轮询模型?

答疑:轮询模型需要不断检查是否有事件发生,浪费 CPU 资源。事件驱动模型基于中断机制——操作系统监测到用户输入(鼠标点击、键盘按下),产生硬件中断,浏览器接收到中断信号后创建事件对象并放入事件队列,事件循环从队列中取出事件进行分发。

论证:

用户点击鼠标
    ↓
操作系统产生中断信号
    ↓
浏览器进程接收信号
    ↓
渲染进程创建 MouseEvent 对象
    ↓
进行命中测试(Hit Testing)确定目标元素
    ↓
按照捕获→目标→冒泡三阶段分发事件
    ↓
执行匹配的事件监听器
1
2
3
4
5
6
7
8
9
10
11
12
13

结果展示:这种设计使得 JavaScript 主线程可以在没有事件时做其他工作(如执行脚本、处理异步任务),只在事件到来时才做出响应,极大提高了效率。这就是为什么 JavaScript 虽然是单线程,但能流畅处理用户交互的原因。

# 8.2 事件监听

# 8.2.1 addEventListener

EventTarget.addEventListener()用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。

const button = document.querySelector("button");

button.addEventListener("click", function (event) {
  console.log("Button clicked!");
});
1
2
3
4
5

该方法接受三个参数。

  • type:事件名称,大小写敏感。
  • listener:监听函数。事件发生时,会调用该监听函数。
  • useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。

# 8.2.2 使用HTML属性

直接在 HTML 元素上定义事件处理函数(不推荐,因为混合了 HTML 和 JavaScript)。

<button onclick="handleClick()">Click me</button>
<script>
  function handleClick() {
    console.log("Button clicked!");
  }
</script>
1
2
3
4
5
6

# 8.2.3 使用DOM属性

通过 DOM 元素的属性绑定事件处理函数(不推荐,因为只能绑定一个处理函数)。

const button = document.querySelector("button");

button.onclick = function (event) {
  console.log("Button clicked!");
};
1
2
3
4
5

三种事件绑定方式的对比:

方式 多个监听器 控制传播阶段 HTML/JS 分离 移除方便
HTML 属性 否 否 否 否
DOM 属性 否 否 是 是
addEventListener 是 是 是 是

# 8.2.4 removeEventListener

EventTarget.removeEventListener()方法用来移除addEventListener()方法添加的事件监听函数。该方法没有返回值。

function handleClick(event) {
  console.log("Button clicked!");
}

button.addEventListener("click", handleClick);
// 移除事件监听器
button.removeEventListener("click", handleClick);
1
2
3
4
5
6
7

注意,removeEventListener()方法移除的监听函数,必须是addEventListener()方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。

element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
1
2

上面代码中,removeEventListener()方法也是无效的,因为第三个参数不一样。

常见错误:匿名函数无法被移除。

// 错误!这两个是不同的函数实例
button.addEventListener('click', () => console.log('click'));
button.removeEventListener('click', () => console.log('click')); // 无效!

// 正确做法:保存函数引用
const handler = () => console.log('click');
button.addEventListener('click', handler);
button.removeEventListener('click', handler); // 有效
1
2
3
4
5
6
7
8

# 8.2.5 dispatchEvent

EventTarget.dispatchEvent()方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。

target.dispatchEvent(event)
1

dispatchEvent()方法的参数是一个Event对象的实例(详见《Event 对象》章节)。

para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);
1
2
3

上面代码在当前节点触发了click事件。

# 8.2.6 addEventListener高级选项

addEventListener 的第三个参数可以是一个选项对象,提供更精细的控制:

element.addEventListener('click', handler, {
  capture: false,    // 是否在捕获阶段触发
  once: true,        // 只触发一次,然后自动移除
  passive: true,     // 不会调用 preventDefault()(性能优化)
  signal: controller.signal  // 通过 AbortController 取消
});

// once 的妙用——一次性事件
button.addEventListener('click', () => {
  console.log('只触发一次');
}, { once: true });

// passive 的性能优化——滚动事件
// 告诉浏览器不会调用 preventDefault(),浏览器可以立即开始滚动
document.addEventListener('touchstart', handler, { passive: true });

// 使用 AbortController 批量移除事件
const controller = new AbortController();

button.addEventListener('click', handler1, { signal: controller.signal });
button.addEventListener('mouseover', handler2, { signal: controller.signal });
input.addEventListener('input', handler3, { signal: controller.signal });

// 一次性移除所有事件
controller.abort();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 8.3 事件对象

事件处理函数会接收一个事件对象(event),它包含与事件相关的信息。

button.addEventListener("click", function (event) {
  console.log(event.target); // 触发事件的元素
  console.log(event.type); // 事件类型(如 "click")
  console.log(event.clientX, event.clientY); // 鼠标位置
});
1
2
3
4
5

# 8.3.1 事件对象常用属性

属性/方法 说明
event.type 事件类型(如 "click"、"keydown")
event.target 触发事件的原始元素
event.currentTarget 当前正在处理事件的元素(绑定监听器的元素)
event.bubbles 事件是否冒泡
event.cancelable 事件是否可以取消默认行为
event.eventPhase 事件当前所在阶段(1=捕获,2=目标,3=冒泡)
event.timeStamp 事件创建时间
event.preventDefault() 阻止默认行为
event.stopPropagation() 阻止事件继续传播
event.stopImmediatePropagation() 阻止传播,且阻止同元素上其他监听器执行
event.composedPath() 返回事件经过的所有节点路径
// event.composedPath() 示例
document.addEventListener('click', (e) => {
  console.log(e.composedPath());
  // [button, div#container, body, html, document, Window]
});

// event.target vs event.currentTarget
document.getElementById('parent').addEventListener('click', function(e) {
  console.log('target:', e.target.id);        // 实际被点击的元素
  console.log('currentTarget:', e.currentTarget.id); // 绑定监听器的元素
});
1
2
3
4
5
6
7
8
9
10
11

# 8.4 事件传播

事件传播分为三个阶段:

  1. 捕获阶段(Capture Phase):从 window 向下传播到目标元素。
  2. 目标阶段(Target Phase):到达目标元素。
  3. 冒泡阶段(Bubble Phase):从目标元素向上传播到 window。

# 8.4.1 捕获阶段

在捕获阶段监听事件,将 addEventListener 的第三个参数设为 true。

document.addEventListener("click", function (event) {
  console.log("Document clicked (capture)");
}, true);
1
2
3

# 8.4.2 冒泡阶段

默认情况下,事件在冒泡阶段触发。

document.addEventListener("click", function (event) {
  console.log("Document clicked (bubble)");
});
1
2
3

# 8.4.3 阻止事件传播

使用 event.stopPropagation() 阻止事件传播。

button.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log("Button clicked (propagation stopped)");
});
1
2
3
4

stopPropagation vs stopImmediatePropagation:

const btn = document.getElementById('btn');

// 给同一个元素绑定两个监听器
btn.addEventListener('click', (e) => {
  console.log('监听器 1');
  e.stopPropagation(); // 只阻止向父元素传播
  // e.stopImmediatePropagation(); // 阻止传播 + 阻止监听器2执行
});

btn.addEventListener('click', (e) => {
  console.log('监听器 2'); // 使用 stopPropagation 时仍会执行
});
1
2
3
4
5
6
7
8
9
10
11
12

# 8.4.4 事件传播的底层原理

疑惑:为什么事件传播要分为捕获和冒泡两个阶段?只有冒泡不行吗?

答疑:这是历史原因。早期浏览器大战中,Netscape 实现了捕获模型(从外到内),IE 实现了冒泡模型(从内到外)。W3C 在制定标准时,为了兼顾两种模型,决定采用捕获 + 冒泡的完整模型。

论证:

<div id="grandparent">
  <div id="parent">
    <button id="child">Click me</button>
  </div>
</div>

<script>
const log = (phase, id) => 
  console.log(`${phase}: ${id}`);

['grandparent', 'parent', 'child'].forEach(id => {
  const el = document.getElementById(id);
  
  // 捕获阶段
  el.addEventListener('click', () => log('捕获', id), true);
  // 冒泡阶段
  el.addEventListener('click', () => log('冒泡', id), false);
});

// 点击 button 后的输出:
// 捕获: grandparent
// 捕获: parent
// 捕获: child(目标阶段,按注册顺序执行)
// 冒泡: child(目标阶段)
// 冒泡: parent
// 冒泡: grandparent
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

结果展示:在目标阶段,捕获和冒泡的区分消失了——监听器按照注册顺序执行。这是一个容易被忽略的细节。

# 8.5 事件委托

事件委托利用事件冒泡机制,将事件监听器绑定到父元素,从而处理子元素的事件。

# 8.5.1 事件委托的原理与实现

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
<script>
  const list = document.getElementById("list");

  list.addEventListener("click", function (event) {
    if (event.target.tagName === "LI") {
      console.log("Clicked on:", event.target.textContent);
    }
  });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

事件委托的优势:

  1. 减少内存占用:1 个监听器替代 N 个监听器
  2. 动态元素支持:新增的子元素自动被委托处理
  3. 简化代码:不需要为每个子元素单独绑定/解绑
// 没有事件委托:每个 li 都需要绑定
document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', handler); // 100个 li = 100个监听器
});

// 使用事件委托:只需一个监听器
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.matches('li')) {
    handler.call(e.target, e);
  }
});

// 动态添加元素也能生效
const newLi = document.createElement('li');
newLi.textContent = 'New Item';
list.appendChild(newLi); // 不需要额外绑定事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 8.5.2 通用事件委托函数

function delegate(parent, eventType, selector, handler) {
  parent.addEventListener(eventType, function(event) {
    // 从 target 向上查找匹配的元素
    let target = event.target;
    while (target && target !== parent) {
      if (target.matches(selector)) {
        handler.call(target, event);
        return;
      }
      target = target.parentElement;
    }
  });
}

// 使用
delegate(document.body, 'click', '.btn', function(e) {
  console.log('按钮被点击:', this.textContent);
});

delegate(document.body, 'click', 'a[data-action]', function(e) {
  e.preventDefault();
  console.log('操作:', this.dataset.action);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 8.6 自定义事件

# 8.6.1 CustomEvent详解

可以通过 CustomEvent 创建和触发自定义事件。

const button = document.querySelector("button");

// 创建自定义事件
const customEvent = new CustomEvent("customClick", {
  detail: { message: "This is a custom event" },
  bubbles: true,       // 允许冒泡
  cancelable: true,    // 允许取消
  composed: false      // 是否穿过 Shadow DOM 边界
});

// 监听自定义事件
button.addEventListener("customClick", function (event) {
  console.log(event.detail.message); // This is a custom event
});

// 触发自定义事件
button.dispatchEvent(customEvent);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

自定义事件的实际应用——组件通信:

// 子组件发出事件
class SearchBox extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<input type="text"><button>搜索</button>';
    this.querySelector('button').addEventListener('click', () => {
      const query = this.querySelector('input').value;
      this.dispatchEvent(new CustomEvent('search', {
        detail: { query },
        bubbles: true  // 冒泡到父组件
      }));
    });
  }
}
customElements.define('search-box', SearchBox);

// 父组件监听
document.addEventListener('search', (e) => {
  console.log('搜索:', e.detail.query);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 8.6.2 非DOM的事件系统

在不依赖 DOM 的场景下(如 Node.js 或纯逻辑模块),可以手动实现事件系统:

class EventEmitter {
  constructor() {
    this._events = new Map();
  }

  on(event, listener) {
    if (!this._events.has(event)) {
      this._events.set(event, []);
    }
    this._events.get(event).push(listener);
    return this; // 支持链式调用
  }

  off(event, listener) {
    const listeners = this._events.get(event);
    if (listeners) {
      this._events.set(event, listeners.filter(fn => fn !== listener && fn._original !== listener));
    }
    return this;
  }

  emit(event, ...args) {
    const listeners = this._events.get(event);
    if (listeners) {
      listeners.forEach(fn => fn.apply(this, args));
    }
    return this;
  }

  once(event, listener) {
    const wrapper = (...args) => {
      listener.apply(this, args);
      this.off(event, wrapper);
    };
    wrapper._original = listener; // 保存原始引用,方便 off
    return this.on(event, wrapper);
  }
  
  removeAllListeners(event) {
    if (event) {
      this._events.delete(event);
    } else {
      this._events.clear();
    }
    return this;
  }
  
  listenerCount(event) {
    const listeners = this._events.get(event);
    return listeners ? listeners.length : 0;
  }
}

// 使用
const bus = new EventEmitter();
bus.on('data', (msg) => console.log('收到:', msg));
bus.once('ready', () => console.log('就绪'));
bus.emit('data', 'Hello');  // 收到: Hello
bus.emit('ready');           // 就绪
bus.emit('ready');           // (无输出,once 已自动移除)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 8.7 事件类型

# 8.7.1 常见DOM事件

常见的 DOM 事件类型包括:

  • 鼠标事件:click, dblclick, mouseover, mouseout, mousedown, mouseup, mousemove, mouseenter, mouseleave, contextmenu
  • 键盘事件:keydown, keyup, keypress(已弃用)
  • 表单事件:submit, change, focus, blur, input, reset, select
  • 窗口事件:load, DOMContentLoaded, unload, beforeunload, resize, scroll
  • 触摸事件:touchstart, touchmove, touchend, touchcancel
  • 拖放事件:dragstart, drag, dragenter, dragleave, dragover, drop, dragend
  • 剪贴板事件:copy, cut, paste

# 8.7.2 最佳实践

  1. 使用事件委托:减少事件监听器的数量,提高性能。
  2. 避免内联事件:将 JavaScript 代码与 HTML 分离。
  3. 移除无用的事件监听器:防止内存泄漏。
  4. 使用 event.preventDefault():阻止默认行为(如表单提交、链接跳转)。
  5. 考虑事件传播:理解捕获和冒泡阶段,避免意外行为。
  6. 使用 passive: true:在滚动和触摸事件中优化性能。
  7. 使用防抖/节流:对高频事件(scroll、resize、input)进行优化。
// 防抖应用:搜索框输入
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  console.log('搜索:', e.target.value);
}, 300));

// 节流应用:滚动事件
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

window.addEventListener('scroll', throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 100), { passive: true });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 8.7.3 表单验证

<form id="myForm">
  <input type="text" id="username" placeholder="Username" required />
  <button type="submit">Submit</button>
</form>
<script>
  const form = document.getElementById("myForm");

  form.addEventListener("submit", function (event) {
    event.preventDefault(); // 阻止表单提交
    const username = document.getElementById("username").value;

    if (username.length < 3) {
      alert("Username must be at least 3 characters long!");
    } else {
      alert("Form submitted successfully!");
    }
  });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 8.7.4 鼠标和键盘事件详解

鼠标事件的坐标系统:

element.addEventListener('click', (e) => {
  // 相对于视口(可视区域)
  console.log(e.clientX, e.clientY);
  
  // 相对于页面(包含滚动偏移)
  console.log(e.pageX, e.pageY);
  
  // 相对于屏幕
  console.log(e.screenX, e.screenY);
  
  // 相对于目标元素
  console.log(e.offsetX, e.offsetY);
  
  // 修饰键
  console.log(e.ctrlKey, e.shiftKey, e.altKey, e.metaKey);
  
  // 鼠标按键
  console.log(e.button); // 0=左键, 1=中键, 2=右键
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

mouseenter/mouseleave vs mouseover/mouseout:

// mouseover/mouseout 会在进入/离开子元素时也触发
// mouseenter/mouseleave 只在进入/离开目标元素时触发(不冒泡)
parent.addEventListener('mouseenter', () => console.log('进入'));
parent.addEventListener('mouseleave', () => console.log('离开'));
1
2
3
4

键盘事件:

document.addEventListener('keydown', (e) => {
  console.log(e.key);     // "a", "Enter", "ArrowUp" 等(推荐使用)
  console.log(e.code);    // "KeyA", "Enter", "ArrowUp"(物理键位)
  console.log(e.keyCode); // 65(已弃用,不推荐)
  
  // 快捷键检测
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault(); // 阻止浏览器保存
    console.log('保存');
  }
});
1
2
3
4
5
6
7
8
9
10
11
上次更新: 2026/06/10, 11:13:41
异步操作
错误机制

← 异步操作 错误机制→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式