Event 事件

1. 事件中的 Event 对象

Event 对象是事件触发时自动创建的核心对象,包含事件相关的所有状态信息,其核心特性如下:

  • 自动创建:只要有事件发生(如点击、滚动、输入),浏览器就会自动生成一个 Event 对象。
  • 核心作用:记录事件触发时的关键信息,例如:
    • 事件发生的目标元素(哪个元素触发了事件);
    • 鼠标位置、键盘按键状态(如 Ctrl 键是否按下);
    • 事件类型(如 clickmouseover)。
  • 执行时机:绑定的事件处理函数不会提前执行,仅在事件触发时,携带 Event 对象作为参数执行。
  • 特殊注意:若在 HTML 标签内通过 onclick 属性直接绑定事件(如 <button onclick="handleClick()">),函数内需通过 window.event 才能获取 Event 对象,而非参数接收。

2. Event 对象常见属性与方法

类别 属性 / 方法 作用描述
目标相关 target 获取触发事件的元素(事件源,可能是绑定事件元素的子元素)
currentTarget 获取绑定事件的元素(与 this 效果一致,不受事件冒泡影响)
事件信息 type 获取事件类型(如 clickscrollkeydown
clientX/clientY 获取鼠标相对于浏览器可视区的坐标(不受页面滚动影响)
offsetX/offsetY 获取鼠标相对于事件源元素(target)左上角的坐标
行为控制 preventDefault() 阻止事件的默认行为(如 a 标签跳转、表单提交)
stopPropagation() 阻止事件冒泡(阻止事件向父元素传递)
dataset 获取元素上通过 data-* 定义的自定义属性(如 data-index → e.target.dataset.index

3. 阻止默认事件

部分 HTML 元素存在默认行为(如 a 标签跳转、表单提交、右键菜单弹出),若需自定义交互逻辑,需阻止这些默认行为,常用两种实现方式:

方式 1:return false(适用于 DOM0 级事件绑定)

当通过 on+事件名 或 attachEvent(IE 低版本)绑定事件时,通过 return false 阻止默认行为:

html

预览

<a id="link" href="https://www.baidu.com">百度链接</a>
<script>
  var link = document.getElementById("link");
  // DOM0 级绑定:onclick
  link.onclick = function() {
    alert("点击链接但不跳转");
    return false; // 阻止 a 标签默认跳转行为
  };
</script>
方式 2:event.preventDefault()(适用于 DOM2 级事件绑定)

当通过 addEventListener 绑定事件时,需调用 Event 对象的 preventDefault() 方法:

html

预览

<a id="link" href="https://www.baidu.com">百度链接</a>
<script>
  var link = document.getElementById("link");
  // DOM2 级绑定:addEventListener
  link.addEventListener("click", function(event) {
    // 兼容 IE 低版本:获取 Event 对象
    var ev = event || window.event;
    alert("点击链接但不跳转");
    ev.preventDefault(); // 阻止默认跳转行为
  });
</script>
兼容封装(支持所有浏览器)

为避免浏览器兼容性问题,可封装通用的阻止默认事件函数:

javascript

运行

/**
 * 阻止事件的默认行为(兼容所有浏览器)
 * @param {Event} event - 事件对象
 */
function preventDefault(event) {
  var ev = event || window.event;
  // 标准浏览器
  if (ev.preventDefault) {
    ev.preventDefault();
  } else {
    // IE 低版本(IE6-8)
    ev.returnValue = false;
  }
}

// 使用示例
link.addEventListener("click", function(event) {
  preventDefault(event); // 直接调用封装函数
  alert("阻止默认跳转");
});

4. 事件流:事件冒泡与事件捕获

事件流描述了事件在 DOM 树中传递的过程,分为 事件捕获目标阶段事件冒泡 三个阶段,主流浏览器均遵循 W3C 标准的 DOM 事件流。

4.1 事件冒泡(默认行为)
  • 定义:事件从触发事件的元素(最具体的子元素)开始,逐级向上传递到 DOM 根节点(如 div → body → html → document → window),类似水泡从水底上升到水面。
  • 浏览器差异
    • IE 6.0:冒泡到 document 为止;
    • 其他浏览器(Chrome、Firefox、Edge):冒泡到 window
  • 注意:并非所有事件都支持冒泡,如 blur(失去焦点)、focus(获取焦点)、load(加载完成)、unload(卸载)等事件不冒泡。
4.2 事件冒泡示例

html

预览

<style>
  #father {
    width: 300px;
    height: 300px;
    background: red;
    margin: 50px auto;
  }
  #btn {
    margin: 100px;
    padding: 10px 20px;
  }
</style>
<div id="father">
  <button id="btn">点击按钮</button>
</div>

<script>
  window.onload = function() {
    var father = document.getElementById("father");
    var btn = document.getElementById("btn");

    // 子元素:按钮点击事件
    btn.onclick = function() {
      alert("按钮被点击(子元素)");
    };

    // 父元素:div 点击事件
    father.onclick = function() {
      alert("div 被点击(父元素)");
    };

    // 根节点:document 点击事件
    document.onclick = function() {
      alert("文档被点击(根节点)");
    };
  };
</script>

  • 执行结果:点击按钮时,会依次弹出 按钮被点击 → div 被点击 → 文档被点击,体现事件从子元素向父元素冒泡的过程。
4.3 阻止事件冒泡

若需避免事件传递到父元素,需阻止冒泡,两种实现方式:

  • 标准浏览器:event.stopPropagation()
  • IE 低版本:event.cancelBubble = true
兼容封装与示例

javascript

运行

/**
 * 阻止事件冒泡(兼容所有浏览器)
 * @param {Event} event - 事件对象
 */
function stopPropagation(event) {
  var ev = event || window.event;
  // 标准浏览器
  if (ev.stopPropagation) {
    ev.stopPropagation();
  } else {
    // IE 低版本
    ev.cancelBubble = true;
  }
}

// 使用示例(修改上述冒泡案例)
btn.onclick = function(event) {
  stopPropagation(event); // 阻止冒泡
  alert("按钮被点击(子元素)"); // 仅执行此弹窗,父元素事件不触发
};
4.4 事件捕获
  • 定义:与事件冒泡相反,事件从 DOM 根节点(如 window)开始,逐级向下传递到触发事件的子元素,即 “从外到内”。
  • 开启方式:通过 addEventListener 的第三个参数 useCapture 控制,useCapture: true 表示开启事件捕获,默认 false(事件冒泡)。
事件捕获示例

html

预览

<style>
  .body {
    width: 100%;
    height: 400px;
    background: red;
  }
  .wrap {
    width: 300px;
    height: 300px;
    background: green;
    margin: 50px auto;
  }
  .inner {
    width: 100px;
    height: 100px;
    background: blue;
    margin: 100px auto;
  }
</style>
<body class="body">
  <div class="wrap">
    <div class="inner"></div>
  </div>
</body>

<script>
  var body = document.querySelector(".body");
  var wrap = document.querySelector(".wrap");
  var inner = document.querySelector(".inner");

  // 开启事件捕获(useCapture: true)
  body.addEventListener("click", function() {
    console.log("body 捕获阶段触发");
  }, true);

  wrap.addEventListener("click", function() {
    console.log("wrap 捕获阶段触发");
  }, true);

  inner.addEventListener("click", function() {
    console.log("inner 目标阶段触发");
  }, true);
</script>

  • 执行结果:点击蓝色 inner 元素时,控制台输出顺序为 body 捕获 → wrap 捕获 → inner 目标,体现 “从外到内” 的捕获过程。

5. 事件委托(事件代理)

事件委托是基于事件冒泡机制的优化方案,核心思想:将子元素的事件绑定到父元素上,通过父元素统一处理所有子元素的事件

5.1 核心场景

当子元素动态生成(如购物车添加商品、列表动态加载)时,直接给子元素绑定事件会失效,此时需通过父元素代理事件。

5.2 事件委托示例(购物车删除商品)

html

预览

<style>
  * {
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .show {
    width: 400px;
    margin: 50px auto;
  }
  button {
    padding: 8px 16px;
    cursor: pointer;
  }
  li {
    height: 60px;
    line-height: 60px;
    border: 1px solid #ff0000;
    margin: 10px 0;
    padding: 0 20px;
    cursor: pointer;
  }
</style>

<div class="show">
  <button id="addBtn">点击添加商品</button>
  <p id="emptyTip">暂时没有商品</p>
  <ul id="goodsList">
    <li data-index="11">老商品 1(点击删除)</li>
    <li data-index="12">老商品 2(点击删除)</li>
  </ul>
</div>

<script>
  var addBtn = document.getElementById("addBtn");
  var emptyTip = document.getElementById("emptyTip");
  var goodsList = document.getElementById("goodsList");
  var goodsCount = 12; // 商品索引计数器

  // 1. 初始化:判断是否显示“无商品”提示
  function updateEmptyTip() {
    var liList = goodsList.querySelectorAll("li");
    emptyTip.style.display = liList.length === 0 ? "block" : "none";
  }
  updateEmptyTip();

  // 2. 点击添加商品(动态生成子元素)
  addBtn.onclick = function() {
    goodsCount++;
    var li = document.createElement("li");
    li.setAttribute("data-index", goodsCount);
    li.innerHTML = `新商品 ${goodsCount - 12}(点击删除)`;
    goodsList.appendChild(li);
    updateEmptyTip();
  };

  // 3. 事件委托:通过父元素 ul 处理子元素 li 的点击事件
  goodsList.onclick = function(e) {
    var ev = e || window.event;
    var target = ev.target; // 获取触发事件的子元素(li)

    // 判断是否点击的是 li 元素(避免误触发其他子元素)
    if (target.tagName === "LI") {
      var goodsIndex = target.dataset.index; // 获取商品索引
      alert(`删除商品,索引:${goodsIndex}`);
      goodsList.removeChild(target); // 删除当前 li
      updateEmptyTip();
    }
  };
</script>
5.3 事件委托的优缺点
优点 缺点
减少事件注册数量,节省内存(无需给每个子元素绑定事件) 依赖事件冒泡,不支持冒泡的事件(如 blur)无法使用
动态生成的子元素自动拥有事件(无需重新绑定) 层级过深时,冒泡过程可能被中间层阻止(需避免随意阻止冒泡)
简化 DOM 更新时的事件管理(删除子元素无需解绑事件,避免内存泄漏) 需判断触发事件的目标元素,可能出现误判(需精准匹配标签 / 类名)
推荐 “就近委托”(如 ul 代理 li),而非全局委托(如 document 代理所有元素)

6. 事件相关工具函数封装

为提升代码复用性和兼容性,可封装事件绑定、解绑、阻止默认行为、阻止冒泡的通用函数:

javascript

运行

// 1. 绑定事件(兼容所有浏览器)
function addEvent(obj, eventType, handler) {
  if (obj.addEventListener) {
    // 标准浏览器(DOM2 级)
    obj.addEventListener(eventType, handler);
  } else if (obj.attachEvent) {
    // IE 低版本(DOM2 级)
    obj.attachEvent("on" + eventType, handler);
  } else {
    // 最原始方式(DOM0 级)
    obj["on" + eventType] = handler;
  }
}

// 2. 解绑事件(兼容所有浏览器)
function removeEvent(obj, eventType, handler) {
  if (obj.removeEventListener) {
    // 标准浏览器
    obj.removeEventListener(eventType, handler);
  } else if (obj.detachEvent) {
    // IE 低版本
    obj.detachEvent("on" + eventType, handler);
  } else {
    // 最原始方式
    obj["on" + eventType] = null;
  }
}

// 3. 阻止默认事件(已封装,见前文)
function preventDefault(event) {
  var ev = event || window.event;
  ev.preventDefault ? ev.preventDefault() : (ev.returnValue = false);
}

// 4. 阻止事件冒泡(已封装,见前文)
function stopPropagation(event) {
  var ev = event || window.event;
  ev.stopPropagation ? ev.stopPropagation() : (ev.cancelBubble = true);
}

// 使用示例
var btn = document.getElementById("btn");
function handleClick() {
  alert("事件触发");
}

// 绑定事件
addEvent(btn, "click", handleClick);
// 解绑事件
removeEvent(btn, "click", handleClick);

7. e.targetthis 与 e.currentTarget 的区别

三者的核心差异在于 “指向的元素不同”,具体区别如下:

对象 指向的元素 适用场景
e.target 触发事件的元素(事件源) 事件委托中判断子元素
this 绑定事件的元素 普通事件绑定中操作当前元素
e.currentTarget 绑定事件的元素(与 this 一致) 需明确区分绑定元素时使用
示例对比

html

预览

<div class="box" style="width: 200px; height: 200px; background: yellow; margin: 50px auto;">
  外层 box
  <button style="margin: 80px;">内层按钮</button>
</div>
<ul class="list" style="width: 200px; margin: 50px auto;">
  <li>列表项 1</li>
  <li>列表项 2</li>
  <li>列表项 3</li>
</ul>

<script>
  // 案例 1:普通元素(无嵌套子元素)
  var box = document.querySelector(".box");
  box.onclick = function(e) {
    console.log("this:", this); // 指向 box(绑定事件的元素)
    console.log("e.target:", e.target); // 指向 box(触发事件的元素,无嵌套)
    console.log("e.currentTarget:", e.currentTarget); // 指向 box(与 this 一致)
  };

// 案例 2:嵌套元素(父元素绑定事件,子元素触发)
var list = document.querySelector(".list");
list.onclick = function(e) {
  console.log("this:", this); // 指向 list(绑定事件的父元素)
  console.log("e.target:", e.target); // 指向 li(触发事件的子元素)
  console.log("e.currentTarget:", e.currentTarget); // 指向 list(与 this 一致)
};

核心区别总结

对象 指向的元素 典型应用场景
e.target 触发事件的具体元素(事件源) 事件委托中判断点击的子元素类型(如 li/button
this 绑定事件的元素 普通事件处理中操作当前元素(如修改样式、属性)
e.currentTarget 绑定事件的元素(与 this 完全一致) 需要明确区分绑定元素与触发元素时使用

实战场景对比

html

预览

<div class="card" style="border: 1px solid #ccc; padding: 20px; margin: 20px;">
  <h3 class="title">商品标题</h3>
  <p class="price">¥99.00</p>
  <button class="buy-btn">加入购物车</button>
</div>

<script>
var card = document.querySelector('.card');
card.addEventListener('click', function(e) {
  console.log('this:', this); // 始终指向 .card(绑定事件的元素)
  console.log('e.currentTarget:', e.currentTarget); // 与 this 一致,指向 .card
  console.log('e.target:', e.target); // 点击不同区域指向不同元素:
  // 点击标题 → <h3>
  // 点击价格 → <p>
  // 点击按钮 → <button>
  
  // 实际应用:根据 target 执行不同操作
  if (e.target.classList.contains('buy-btn')) {
    console.log('加入购物车');
  } else if (e.target.classList.contains('title')) {
    console.log('查看商品详情');
  }
});
</script>

8. 常用事件类型详解

JavaScript 提供了丰富的事件类型,覆盖用户交互的各种场景,以下是开发中最常用的几类:

8.1 鼠标事件(Mouse Events)

处理鼠标相关操作,如点击、移动、悬停等。

事件名 触发时机 支持冒泡
click 鼠标左键单击元素
dblclick 鼠标左键双击元素
mousedown 鼠标按下(任意键)
mouseup 鼠标抬起(任意键)
mousemove 鼠标在元素上移动
mouseover 鼠标移入元素(含子元素)
mouseout 鼠标移出元素(含子元素)
mouseenter 鼠标移入元素(不含子元素,不冒泡)
mouseleave 鼠标移出元素(不含子元素,不冒泡)
contextmenu 鼠标右键点击(打开上下文菜单前)
应用示例:鼠标悬停效果

html

预览

<style>
.box {
  width: 200px;
  height: 200px;
  background-color: #f0f0f0;
  transition: all 0.3s;
  margin: 50px;
}
.box.active {
  background-color: #4285f4;
  transform: scale(1.1);
}
</style>

<div class="box" id="box"></div>

<script>
var box = document.getElementById('box');

// mouseenter 与 mouseleave(不冒泡,适合精确控制)
box.addEventListener('mouseenter', function() {
  this.classList.add('active');
});
box.addEventListener('mouseleave', function() {
  this.classList.remove('active');
});

// 对比:mouseover 与 mouseout(会冒泡,子元素也会触发)
// box.addEventListener('mouseover', function() { ... });
// box.addEventListener('mouseout', function() { ... });
</script>

8.2 键盘事件(Keyboard Events)

处理键盘输入,常用于表单验证、快捷键等场景。

事件名 触发时机 支持冒泡
keydown 键盘按下(按住会持续触发)
keyup 键盘抬起
keypress 按下字符键(已过时,推荐用 keydown
常用属性
  • key:获取按下的键(如 aEnterArrowUp
  • keyCode:获取键码(数字,如 65 代表 a,已逐步被 key 替代)
  • ctrlKey/shiftKey/altKey:布尔值,判断 Ctrl/Shift/Alt 键是否同时按下
应用示例:快捷键控制

html

预览

<input type="text" id="input" placeholder="按 Ctrl+S 保存">
<div id="status"></div>

<script>
var input = document.getElementById('input');
var status = document.getElementById('status');

document.addEventListener('keydown', function(e) {
  // 检测 Ctrl+S 快捷键
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault(); // 阻止浏览器默认保存行为
    status.textContent = '已保存内容:' + input.value;
  }
  
  // 检测 Esc 键清空输入
  if (e.key === 'Escape') {
    input.value = '';
    status.textContent = '已清空输入';
  }
});
</script>

8.3 表单事件(Form Events)

处理表单元素的交互,如输入、提交、焦点变化等。

事件名 触发时机 支持冒泡
submit 表单提交(点击提交按钮或按 Enter)
reset 表单重置
focus 元素获取焦点(如输入框被点击)
blur 元素失去焦点(如输入框被点击后离开)
input 元素值发生变化(实时触发,如输入文字)
change 元素值发生变化且失去焦点(如下拉框选择)
应用示例:表单验证

html

预览

<form id="loginForm">
  <div>
    <label>用户名:</label>
    <input type="text" name="username" id="username">
    <span class="error" id="userError"></span>
  </div>
  <div>
    <label>密码:</label>
    <input type="password" name="password" id="password">
    <span class="error" id="pwdError"></span>
  </div>
  <button type="submit">登录</button>
</form>

<script>
var form = document.getElementById('loginForm');
var username = document.getElementById('username');
var password = document.getElementById('password');
var userError = document.getElementById('userError');
var pwdError = document.getElementById('pwdError');

// 实时验证用户名
username.addEventListener('input', function() {
  if (this.value.length < 3) {
    userError.textContent = '用户名至少3个字符';
    userError.style.color = 'red';
  } else {
    userError.textContent = '√';
    userError.style.color = 'green';
  }
});

// 表单提交验证
form.addEventListener('submit', function(e) {
  var isValid = true;
  
  // 验证用户名
  if (username.value.length < 3) {
    userError.textContent = '用户名不能为空且至少3个字符';
    isValid = false;
  }
  
  // 验证密码
  if (password.value.length < 6) {
    pwdError.textContent = '密码至少6个字符';
    isValid = false;
  }
  
  if (!isValid) {
    e.preventDefault(); // 验证失败,阻止表单提交
  } else {
    alert('提交成功!');
  }
});
</script>

8.4 窗口事件(Window Events)

处理浏览器窗口相关操作,如加载、 resize、滚动等。

事件名 触发时机 支持冒泡
load 页面所有资源(图片、脚本等)加载完成
DOMContentLoaded DOM 加载完成(无需等待图片等资源)
resize 窗口大小改变时
scroll 页面或元素滚动时
beforeunload 页面即将关闭或刷新前
应用示例:响应式布局与滚动监听

html

预览

<div id="scrollTop" style="position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px; background: #4285f4; color: white; border-radius: 50%; display: none; align-items: center; justify-content: center; cursor: pointer;">↑</div>

<script>
var scrollTopBtn = document.getElementById('scrollTop');

// 监听页面滚动
window.addEventListener('scroll', function() {
  // 滚动距离超过 300px 显示回到顶部按钮
  var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  scrollTopBtn.style.display = scrollTop > 300 ? 'flex' : 'none';
});

// 点击回到顶部
scrollTopBtn.addEventListener('click', function() {
  window.scrollTo({
    top: 0,
    behavior: 'smooth' // 平滑滚动
  });
});

// 监听窗口大小变化
window.addEventListener('resize', function() {
  console.log('窗口宽度:', window.innerWidth);
  console.log('窗口高度:', window.innerHeight);
  // 可根据窗口大小调整布局
});
</script>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐