Appearance
DOM EventListener
EventListener 的概念
EventListener(事件监听器)是一种机制,用于监听和响应 DOM 事件。通过 EventListener,我们可以为元素添加事件处理函数,当事件触发时执行相应的代码。
addEventListener() 方法
addEventListener() 方法用于向元素添加事件监听器:
语法
javascript
element.addEventListener(event, listener, options);参数
- event:事件类型(字符串),如 'click'、'mouseover' 等
- listener:事件处理函数,可以是函数或对象
- options:可选参数,一个对象,包含以下属性:
- capture:布尔值,指定事件是否在捕获阶段触发,默认为 false
- once:布尔值,指定事件监听器是否只执行一次,默认为 false
- passive:布尔值,指定事件监听器是否不会调用 preventDefault(),默认为 false
- signal:AbortSignal 对象,用于取消事件监听器
示例
javascript
const button = document.getElementById("btn");
// 基本用法
button.addEventListener("click", function () {
console.log("Button clicked!");
});
// 带选项
button.addEventListener(
"click",
function () {
console.log("Button clicked (capture)");
},
{ capture: true }
);
// 只执行一次
button.addEventListener(
"click",
function () {
console.log("Button clicked (once)");
},
{ once: true }
);
// 提高滚动性能
window.addEventListener(
"scroll",
function () {
console.log("Scrolled");
},
{ passive: true }
);removeEventListener() 方法
removeEventListener() 方法用于移除元素上的事件监听器:
语法
javascript
element.removeEventListener(event, listener, options);参数
- event:事件类型(字符串),如 'click'、'mouseover' 等
- listener:要移除的事件处理函数,必须与添加时的函数引用相同
- options:可选参数,一个对象,包含以下属性:
- capture:布尔值,指定要移除的事件监听器是否在捕获阶段触发,默认为 false
示例
javascript
const button = document.getElementById("btn");
// 定义事件处理函数
function handleClick() {
console.log("Button clicked!");
}
// 添加事件监听器
button.addEventListener("click", handleClick);
// 移除事件监听器
button.removeEventListener("click", handleClick);
// 注意:匿名函数无法移除
button.addEventListener("click", function () {
console.log("Button clicked!");
});
// 无法移除上面的匿名函数
// button.removeEventListener('click', function() { ... }); // 无效EventListener 对象
除了函数,事件监听器还可以是一个对象,该对象必须包含一个 handleEvent() 方法:
javascript
const button = document.getElementById("btn");
// 创建 EventListener 对象
const listener = {
handleEvent: function (event) {
console.log("Button clicked!");
console.log("Event type:", event.type);
},
};
// 添加事件监听器
button.addEventListener("click", listener);
// 移除事件监听器
button.removeEventListener("click", listener);事件监听器的执行顺序
当多个事件监听器被添加到同一个元素的同一个事件时,它们会按照添加的顺序执行:
javascript
const button = document.getElementById("btn");
button.addEventListener("click", function () {
console.log("First listener");
});
button.addEventListener("click", function () {
console.log("Second listener");
});
button.addEventListener("click", function () {
console.log("Third listener");
});
// 点击按钮时的输出顺序:
// First listener
// Second listener
// Third listener事件监听器的优先级
事件监听器的优先级取决于以下因素:
- 事件阶段:捕获阶段的监听器先于目标阶段,目标阶段先于冒泡阶段
- 添加顺序:同一阶段的监听器按照添加的顺序执行
javascript
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
// 捕获阶段
outer.addEventListener(
"click",
function () {
console.log("Outer (capture)");
},
{ capture: true }
);
// 冒泡阶段
outer.addEventListener("click", function () {
console.log("Outer (bubble)");
});
// 目标阶段(默认冒泡)
inner.addEventListener("click", function () {
console.log("Inner");
});
// 点击 inner 元素时的输出顺序:
// Outer (capture)
// Inner
// Outer (bubble)事件监听器的最佳实践
1. 使用命名函数
使用命名函数而不是匿名函数,以便可以移除事件监听器:
javascript
// 好的做法:使用命名函数
function handleClick() {
console.log("Button clicked!");
}
const button = document.getElementById("btn");
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);
// 不好的做法:使用匿名函数
const button = document.getElementById("btn");
button.addEventListener("click", function () {
console.log("Button clicked!");
});
// 无法移除匿名函数2. 使用事件委托
对于多个相似元素的事件,使用事件委托:
javascript
// 好的做法:使用事件委托
const container = document.getElementById("container");
container.addEventListener("click", function (event) {
if (event.target.classList.contains("item")) {
console.log("Item clicked:", event.target.textContent);
}
});
// 不好的做法:为每个元素添加事件监听器
const items = document.querySelectorAll(".item");
items.forEach((item) => {
item.addEventListener("click", function () {
console.log("Item clicked:", this.textContent);
});
});3. 清理事件监听器
在不需要事件监听器时,及时移除它们,避免内存泄漏:
javascript
class MyComponent {
constructor() {
this.button = document.getElementById("btn");
this.handleClick = this.handleClick.bind(this);
this.button.addEventListener("click", this.handleClick);
}
handleClick() {
console.log("Button clicked!");
}
destroy() {
this.button.removeEventListener("click", this.handleClick);
}
}
const component = new MyComponent();
// 不再需要时清理
component.destroy();4. 使用 passive 选项
对于滚动、触摸等高频事件,使用 passive: true 可以提高性能:
javascript
// 好的做法:使用 passive 选项
window.addEventListener(
"scroll",
function () {
console.log("Scrolled");
},
{ passive: true }
);
// 不好的做法:不使用 passive 选项
window.addEventListener("scroll", function () {
console.log("Scrolled");
});5. 使用 once 选项
对于只需要执行一次的事件,使用 once: true 选项:
javascript
// 好的做法:使用 once 选项
const button = document.getElementById("btn");
button.addEventListener(
"click",
function () {
console.log("Button clicked once!");
},
{ once: true }
);
// 不好的做法:手动移除
const button = document.getElementById("btn");
function handleClick() {
console.log("Button clicked once!");
button.removeEventListener("click", handleClick);
}
button.addEventListener("click", handleClick);6. 使用 AbortSignal 取消事件监听器
使用 AbortSignal 可以更灵活地取消事件监听器:
javascript
// 创建 AbortController
const controller = new AbortController();
const { signal } = controller;
const button = document.getElementById("btn");
// 添加事件监听器
button.addEventListener(
"click",
function () {
console.log("Button clicked!");
},
{ signal }
);
// 取消事件监听器
controller.abort();事件监听器的常见错误
1. 尝试移除匿名函数
无法移除匿名函数作为事件监听器:
javascript
const button = document.getElementById("btn");
// 错误:尝试移除匿名函数
button.addEventListener("click", function () {
console.log("Button clicked!");
});
// 无效:这是一个不同的函数引用
button.removeEventListener("click", function () {
console.log("Button clicked!");
});
// 正确:使用命名函数
function handleClick() {
console.log("Button clicked!");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);2. 忘记绑定 this
在类方法中使用事件监听器时,忘记绑定 this:
javascript
class MyComponent {
constructor() {
this.button = document.getElementById("btn");
// 错误:忘记绑定 this
this.button.addEventListener("click", this.handleClick);
// 正确:绑定 this
// this.handleClick = this.handleClick.bind(this);
// this.button.addEventListener('click', this.handleClick);
// 正确:使用箭头函数
// this.button.addEventListener('click', () => this.handleClick());
}
handleClick() {
// 这里的 this 指向 button 元素,而不是 MyComponent 实例
console.log(this);
}
}3. 事件监听器泄漏
在单页应用中,没有清理事件监听器会导致内存泄漏:
javascript
// 不好的做法:没有清理事件监听器
function setupEventListeners() {
const button = document.getElementById("btn");
button.addEventListener("click", function () {
console.log("Button clicked!");
});
}
// 多次调用会添加多个事件监听器
setupEventListeners();
setupEventListeners();
// 好的做法:清理事件监听器
function setupEventListeners() {
const button = document.getElementById("btn");
function handleClick() {
console.log("Button clicked!");
}
button.addEventListener("click", handleClick);
// 返回清理函数
return function cleanup() {
button.removeEventListener("click", handleClick);
};
}
const cleanup = setupEventListeners();
// 不再需要时清理
cleanup();4. 过度使用事件监听器
过度使用事件监听器会影响性能:
javascript
// 不好的做法:过度使用事件监听器
const items = document.querySelectorAll(".item");
items.forEach((item) => {
item.addEventListener("click", function () {
console.log("Item clicked:", this.textContent);
});
item.addEventListener("mouseover", function () {
this.style.backgroundColor = "yellow";
});
item.addEventListener("mouseout", function () {
this.style.backgroundColor = "";
});
});
// 好的做法:使用事件委托
const container = document.getElementById("container");
container.addEventListener("click", function (event) {
if (event.target.classList.contains("item")) {
console.log("Item clicked:", event.target.textContent);
}
});
container.addEventListener("mouseover", function (event) {
if (event.target.classList.contains("item")) {
event.target.style.backgroundColor = "yellow";
}
});
container.addEventListener("mouseout", function (event) {
if (event.target.classList.contains("item")) {
event.target.style.backgroundColor = "";
}
});小结
EventListener 是 DOM 事件处理的核心机制,它允许我们为元素添加事件处理函数,当事件触发时执行相应的代码。通过 addEventListener() 和 removeEventListener() 方法,我们可以灵活地管理事件监听器。在实际开发中,应该遵循最佳实践,使用事件委托、清理事件监听器、使用适当的选项等,以提高代码的性能和可维护性。