聊一聊JavaScript事件代理和应用场景

389 阅读3分钟

什么是JS事件代理?

JS事件代理(Event Delegation)是一种常用的绑定事件的技巧,它的原理是利用事件冒泡,把原本需要绑定在子元素的响应事件委托给父元素,从而减少事件处理器的数量,提高性能和灵活性。

为什么要使用JS事件代理?

使用JS事件代理有以下几个优点:

  • 减少内存消耗,提高性能。如果有很多子元素需要绑定相同的事件,那么使用事件代理可以避免为每个子元素都创建一个事件处理器,从而节省内存空间和注册时间。
  • 动态添加或删除子元素时无需重新绑定事件。如果使用事件代理,那么只需要在父元素上绑定一次事件,就可以自动适应子元素的变化,无论是新增还是删除子元素,都不影响事件的触发。
  • 方便修改或扩展功能。如果使用事件代理,那么只需要修改或添加父元素的事件处理器,就可以实现对所有子元素的功能修改或扩展,而不需要逐个修改或添加子元素的事件处理器。

JS事件代理的应用场景

最近在学习React所以下面的示例用它来写...

列表或表格中的操作按钮

如果有一个列表或表格中有很多行,每行都有一个操作按钮,例如删除或编辑等,那么可以使用事件代理来为所有的按钮绑定一个点击事件,而不需要为每个按钮都创建一个点击事件。

import React from 'react';

class Table extends React.Component {
  handleClick = (e) => {
    if (e.target.nodeName === 'BUTTON') {
      const id = e.target.getAttribute('data-id');
      // 处理点击事件
      console.log(`delete row ${id}`);
    }
  }

  render() {
    const data = [
      { id: 1, name: 'John Doe', age: 30 },
      { id: 2, name: 'Jane Smith', age: 25 },
      { id: 3, name: 'Bob Johnson', age: 40 }
    ];

    return (
      <table onClick={this.handleClick}>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Age</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {data.map(row => (
            <tr key={row.id}>
              <td>{row.id}</td>
              <td>{row.name}</td>
              <td>{row.age}</td>
              <td><button data-id={row.id}>Delete</button></td>
            </tr>
          ))}
        </tbody>
      </table>
    );
  }
}

export default Table;

导航栏或菜单栏中的链接

如果有一个导航栏或菜单栏中有很多链接,每个链接都需要跳转到不同的页面或执行不同的功能,那么可以使用事件代理来为所有的链接绑定一个点击事件,而不需要为每个链接都创建一个点击事件。

import React from 'react';

class Navigation extends React.Component {
  handleClick = (e) => {
    e.preventDefault();
    const path = e.target.getAttribute('href');
    // 处理点击事件
    console.log(`navigate to ${path}`);
  }

  render() {
    const links = [
      { label: 'Home', path: '/' },
      { label: 'About', path: '/about' },
      { label: 'Contact', path: '/contact' }
    ];

    return (
      <nav onClick={this.handleClick}>
        <ul>
          {links.map(link => (
            <li key={link.path}>
              <a href={link.path}>{link.label}</a>
            </li>
          ))}
        </ul>
      </nav>
    );
  }
}

export default Navigation;

动态生成的内容

如果有一些内容是动态生成的,例如通过AJAX获取数据后渲染到页面上,那么可以使用事件代理来为这些内容绑定事件,而不需要在每次生成内容后都重新绑定事件。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function List() {
  const [list, setList] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await axios.get('https://example.com/api/list');
      setList(response.data);
    }
    fetchData();
  }, []);

  function handleDelete(e) {
    const itemId = e.target.dataset.id;
    setList(list.filter(item => item.id !== itemId));
  }

  return (
    <ul onClick={handleDelete}>
      {list.map(item => (
        <li key={item.id}>
          <span>{item.name}</span>
          <button data-id={item.id}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

export default List;

在上面的示例代码中,点击删除按钮会触发父元素ulclick事件,然后通过事件对象获取到被点击的按钮,进一步获取到对应的列表项的id,最后从列表中删除该项。这种方式避免了为每个删除按钮都创建一个点击事件的问题。

总结

使用事件代理存在几个优点:

  • 减少整个页面所需的内存,提升整体性能
  • 动态绑定,减少重复工作

使用事件代理也存在局限性:

  • focusblur这些事件没有事件冒泡机制,所以无法进行委托绑定事件
  • mousemovemouseout这样的事件,需要不断通过位置去计算定位,对性能消耗高,因此不适合代理