理解了事件流(捕获与冒泡)之后,紧接着就需要知道如何把一个函数注册到某个事件上,以及如何在不必要时把它移除。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 级的优势
- 可以为同一个事件绑定多个处理函数
不再相互覆盖,会按绑定顺序依次执行。
- 可以精确控制触发阶段
通过 capture 参数指定在捕获还是冒泡阶段触发,满足各种场景下的精细化需求。
- 可以动态移除指定的事件处理函数
通过 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);
- 支持 passive 选项优化滚动性能
对于 touchstart、scroll、wheel 等事件,将 passive 设为 true 可以告诉浏览器你不会调用 preventDefault(),从而让页面滚动更流畅(尤其在移动端)。这是 DOM0 级无法做到的。
document.addEventListener('touchstart', handleTouch, { passive: true });
- 可传入事件处理对象(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 实现了自己的方法:attachEvent 和 detachEvent,语法和 addEventListener 类似,但有几个关键差异:
- 事件名带
on前缀:'onclick'。 - 只支持冒泡阶段。
- 事件处理函数中的
this指向全局window,而非绑定元素。 - 多次绑定的执行顺序与绑定顺序相反(后绑定先执行)。
这些差异在现代开发中已基本无需在意,除非你还在维护极老的浏览器项目。如今所有主流浏览器都支持标准的 addEventListener,直接用即可。
15.2.4 实践中的选择
| 场景 | 推荐方式 |
|------|---------|
| 简单 demo、教学示例 | DOM0 级直接赋值,简单粗暴 |
| 正式项目、需要多个处理函数 | addEventListener |
| 需要监听捕获阶段 | addEventListener |
| 需要滚动性能优化(移动端) | addEventListener + passive |
| 只执行一次的事件 | addEventListener + once 或 DOM0 级配合 null 手动移除 |
| 动态添加/移除元素的事件 | 使用事件委托(下一节会讲),而非单独绑定 |
总体而言,在现代 JavaScript 开发中,addEventListener 已成为事实标准。DOM0 级因其简洁性在某些零碎场景中仍然可见,但一旦涉及组件化的 UI 系统、动态元素、复杂交互,DOM2 级几乎是不二选择。掌握它,你就能写出健壮且易于维护的事件驱动代码。