如何减少事件的绑定个数?

441 阅读4分钟

提出问题

如果一个列表(ul)中的的每一个子项(li)都有同样的点击事件你会如何编写代码呢?

  1. 每一个li都添加点击事件
  <ul class="list">
    <li class="list-item">我在等世间唯一契合的灵魂</li>
    <li class="list-item">能哭的地方只有厕所和爸爸的怀里</li>
    <li class="list-item">你将不再是工具,而是名副其实的人</li>
    <li class="list-item">我为何在哭泣</li>
    <li class="list-item">如果这一切是梦就好了</li>
    <li class="list-item">那女孩对我说</li>
    <li class="list-item">我是否走进了你的心房?没脱鞋就进来了呢!</li>
    <li class="list-item">前天是小鹿,昨天是小兔子,今天是你。 </li>
  </ul>
  <script>
    const lis = document.querySelectorAll('.list-item')
    lis.forEach(li => {
      li.addEventListener('click', () => {
        console.log(li.innerHTML) // 点击li,控制台会输出相应的句子
      })
    })
  1. 在li 的父容器ul 上添加点击事件

利用事件的冒泡,点击li,事件会沿着DOM 树向上冒泡到document/window

    // 页面结构同上
    const ul = document.querySelector('.list')
    ul.addEventListener('click', (e) => {
      console.log(e.target.innerHTML) // 点击li,控制台会输出相应的句子
    })

问题升级

如果列表的子节点更加复杂呢?我只想点击第二个span(.words) 会在控制台输出相应句子。

  <ul class="list">
    <li class="list-item">
      <span>晚婚</span>
      <span class="words">我在等世间唯一契合的灵魂</span>
    </li>
    <li class="list-item">
      <span>CLANNAD</span>
      <span class="words">能哭的地方只有厕所和爸爸的怀里</span>
    </li>
    <li class="list-item">
      <span>紫罗兰的永恒花园</span>
      <span class="words">你将不再是工具,而是名副其实的人</span>
    </li>
    <li class="list-item">
      <span>紫罗兰的永恒花园</span>
      <span class="words">我为何在哭泣</span>
    </li>
    <li class="list-item">
      <span>Lemon</span>
      <span class="words">如果这一切是梦就好了</span>
    </li>
    <li class="list-item">
      <span>那女孩对我说</span>
      <span class="words">那女孩对我说</span>
    </li>
    <li class="list-item">
      <span>四月是你的谎言</span>
      <span class="words">我是否走进了你的心房?没脱鞋就进来了呢!</span>
    </li>
    <li class="list-item">
      <span>CLANNAD</span>
      <span class="words">前天是小鹿,昨天是小兔子,今天是你。</span>
    </li>
  </ul>

基本思路

通过事件对象的一些属性和目标元素的一些特征来找到目标元素

  1. 利用event 对象中的path 属性(path 有些浏览器不兼容)
    const ul = document.querySelector('.list')
    ul.addEventListener('click', (e) => {
      // console.log(e.path)
      const path = e.path
      path.forEach(el => {
        // 根据目标元素的特有的东西来判断是否是目标元素,这里特有的是类名为words
        // 判断是否为元素节点,如果是,再查看是否存在class 属性
        if (el.nodeType === 1 && el.hasAttribute('class')) {
          const className = el.getAttribute('class')
          // 类名为words 就是目标元素
          if (className === 'words') {
            console.log(el.innerHTML)
          }
        }
      })
    })
  1. 使用event.target来确定目标元素
    const ul = document.querySelector('.list')
    ul.addEventListener('click', (e) => {
      const target = e.target
      // console.log(target)
      if (target.nodeType === 1 && target.hasAttribute('class')) {
          const className = target.getAttribute('class')
          // 类名为words 就是目标元素
          if (className === 'words') {
            console.log(target.innerHTML)
          }
        }
    })

为什么需要减少绑定事件的个数

绑定事件需要消耗性能。如果有一个可以下拉加载更多的列表,你向每一个li 都添加一个点击事件,那么这个页面一定会越来越卡。

总结

  1. 添加的事件过多会影响性能,造成页面卡顿
  2. 可以利用事件冒泡机制来减少事件数量
  3. DOM结构复杂时,可以通过event 对象的path 属性来确定目标节点,然后做对应操作
  4. 事件对象的一些属性存在兼容性问题
  5. event.target是触发事件的元素,event.currentTarget是事件绑定的元素
  6. 低版本IE浏览器不支持addEventListener...(IE是万恶之源)