人人都会AI编程

15.2 事件绑定与移除:DOM0 级、DOM2 级事件

更新时间:2026-07-11

理解了事件流(捕获与冒泡)之后,紧接着就需要知道如何把一个函数注册到某个事件上,以及如何在不必要时把它移除。JavaScript 在历史上提供了多种事件绑定方式,其中最具代表性的是 DOM0 级DOM2 级。这两种方式在设计理念和使用细节上有着本质区别,搞清楚了它们,就能在实际开发中写出更稳定、可维护的代码。

15.2.1 DOM0 级事件绑定

DOM0 级并不是一个正式标准,而是指最早期浏览器就已支持的事件绑定方式。它的核心思想非常简单:把事件处理函数直接赋值给 DOM 元素上的 on 前缀属性

<button id="btn">点我</button>
<script>
  const btn = document.getElementById('btn');
  
  // 绑定事件:直接将函数赋给 onclick
  btn.onclick = function() {
    console.log('按钮被点击了');
  };
</script>

你也可以直接在 HTML 标签中以内联方式书写,本质上等价于字符串代码执行(但出于结构与行为分离、安全性等考虑,现代开发中已极少使用):

<button onclick="console.log('点击')">点我</button>

DOM0 级的特点

| 特性 | 描述 |
|------|------|
| 简单直接 | 一行赋值就能绑定事件,对初学者非常友好。 |
| 只能绑定一个处理函数 | 同一个元素的同一个事件(如 onclick)只能有一个处理函数。如果重复赋值,后赋值的会覆盖先赋值的。 |
| 发生在冒泡阶段 | DOM0 级事件默认在冒泡阶段触发,无法指定捕获阶段。 |
| 移除很方便 | 只需将属性设为 null 即可解绑:btn.onclick = null; |

覆盖问题是最容易踩坑的地方:

btn.onclick = function() { console.log('第一次绑定'); };
btn.onclick = function() { console.log('第二次绑定'); };  // 覆盖了上面
// 点击后只输出 "第二次绑定"

如果你需要绑定多个处理函数,或者需要精细控制捕获/冒泡,DOM0 级就无法胜任了。

15.2.2 DOM2 级事件绑定

DOM2 级事件规范带来了 addEventListener()removeEventListener() 两个方法,它们在 IE9+ 及所有现代浏览器中均被支持,是目前标准且推荐的事件绑定方式。

核心语法

target.addEventListener(type, listener, options);

参数解释:

  • type:事件名称,不带 on 前缀,如 'click''keydown'
  • listener:事件处理函数,可以是一个普通函数、箭头函数或者实现了 EventListener 接口的对象(带有 handleEvent 方法的对象)。
  • options(可选):一个配置对象或布尔值。
  • 若为布尔值 true,表示在捕获阶段调用监听器;false(或不传)表示在冒泡阶段调用。
  • 若为对象,支持三个属性:capture(是否捕获)、once(是否只执行一次)、passive(是否不调用 preventDefault(),用于提升滚动性能)。
// 在冒泡阶段绑定点击事件
btn.addEventListener('click', function() {
  console.log('按钮被点击了');
}, false);

// 在捕获阶段绑定(第三个参数为 true)
btn.addEventListener('click', function() {
  console.log('捕获阶段触发');
}, true);

// 使用 options 对象:只执行一次
btn.addEventListener('click', function() {
  console.log('只会执行一次');
}, { once: true });

DOM2 级的优势

  1. 可以为同一个事件绑定多个处理函数

不再相互覆盖,会按绑定顺序依次执行。

  1. 可以精确控制触发阶段

通过 capture 参数指定在捕获还是冒泡阶段触发,满足各种场景下的精细化需求。

  1. 可以动态移除指定的事件处理函数

通过 removeEventListener 移除时必须传入完全相同的参数(事件名、函数引用、捕获标志),否则无法移除。这是最重要的坑点——函数引用必须一致。

function handleClick() {
  console.log('处理点击');
}
// 绑定
btn.addEventListener('click', handleClick);
// 之后移除(必须传入同一个函数引用)
btn.removeEventListener('click', handleClick);

注意:如果使用了匿名函数或绑定了 this 的函数,几乎无法被移除,因为拿不到原来的引用。

// ❌ 这样绑定后无法移除,因为函数引用丢失了
btn.addEventListener('click', function() { console.log(1); });
btn.removeEventListener('click', function() { console.log(1); }); // 无效!

// ✅ 正确方式:保留函数引用
const handler = (e) => console.log(1);
btn.addEventListener('click', handler);
// 适时移除
btn.removeEventListener('click', handler);
  1. 支持 passive 选项优化滚动性能

对于 touchstartscrollwheel 等事件,将 passive 设为 true 可以告诉浏览器你不会调用 preventDefault(),从而让页面滚动更流畅(尤其在移动端)。这是 DOM0 级无法做到的。

document.addEventListener('touchstart', handleTouch, { passive: true });
  1. 可传入事件处理对象(EventListener 接口)

如果一个对象实现了 handleEvent 方法,也可以作为监听器,方便 OOP 风格的封装。

const eventHandlerObj = {
  handleEvent(e) {
    console.log('事件触发', e.type);
  }
};
btn.addEventListener('click', eventHandlerObj);

移除事件时的注意事项

  • removeEventListener 必须使用与绑定时完全一致的参数。
  • 若绑定时使用了 { once: true },事件触发一次后会自动移除,无需手动调用 removeEventListener
  • 对于使用了 { passive: true } 的监听器,移除时要传入相同的 options 对象,但主流浏览器对 passive 的匹配比较宽松,重点还是函数引用一致

15.2.3 IE 专有事件(了解即可)

在旧版 IE(IE8 及更早)中,Microsoft 实现了自己的方法:attachEventdetachEvent,语法和 addEventListener 类似,但有几个关键差异:

  • 事件名带 on 前缀:'onclick'
  • 只支持冒泡阶段。
  • 事件处理函数中的 this 指向全局 window,而非绑定元素。
  • 多次绑定的执行顺序与绑定顺序相反(后绑定先执行)。

这些差异在现代开发中已基本无需在意,除非你还在维护极老的浏览器项目。如今所有主流浏览器都支持标准的 addEventListener,直接用即可。

15.2.4 实践中的选择

| 场景 | 推荐方式 |
|------|---------|
| 简单 demo、教学示例 | DOM0 级直接赋值,简单粗暴 |
| 正式项目、需要多个处理函数 | addEventListener |
| 需要监听捕获阶段 | addEventListener |
| 需要滚动性能优化(移动端) | addEventListener + passive |
| 只执行一次的事件 | addEventListener + once 或 DOM0 级配合 null 手动移除 |
| 动态添加/移除元素的事件 | 使用事件委托(下一节会讲),而非单独绑定 |

总体而言,在现代 JavaScript 开发中,addEventListener 已成为事实标准。DOM0 级因其简洁性在某些零碎场景中仍然可见,但一旦涉及组件化的 UI 系统、动态元素、复杂交互,DOM2 级几乎是不二选择。掌握它,你就能写出健壮且易于维护的事件驱动代码。