Skip to content

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

事件监听器的优先级

事件监听器的优先级取决于以下因素:

  1. 事件阶段:捕获阶段的监听器先于目标阶段,目标阶段先于冒泡阶段
  2. 添加顺序:同一阶段的监听器按照添加的顺序执行
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() 方法,我们可以灵活地管理事件监听器。在实际开发中,应该遵循最佳实践,使用事件委托、清理事件监听器、使用适当的选项等,以提高代码的性能和可维护性。