2020-04-26 事件委托(事件代理)

94 阅读1分钟

场景:

有个列表 ul ,要给 ul 下每一个 li 添加点击事件。

这是一个非常常见的需求,我们需要考虑的有两点,一是性能问题,二是 li 动态添加问题。

不推荐的做法:逐个给 li 添加监听事件,由于事件冒泡的原因,会增加非常多的DOM操作,其次动态添加进来的 li 还得再重新绑事件。

推荐做法:事件委托(也叫事件代理)。

直白的说法就是将监听事件绑在 ul 上,然后通过Event对象提供的target属性获取到事件的目标节点 进行判断是否是 li 元素。


下面上代码:

HTML片段:

      <ul>
        <li>111</li>
        <li><span>222</span></li>
      </ul>

代码一:(这段代码很常见,但是是有问题的)

      document.querySelectorAll("ul")[0].addEventListener("click", function (e) {
        if (e.target.tagName.toLowerCase() === "li") {
          console.log(e.target.innerText);
        }
      });

这段代码的bug在于如果用户点击的是 li 里面的 span,就没法触发,这显然是不对的(即点击第一个ok,第二个不行)

代码二:

      function delegate(element, eventType, selector, fn) {
        element.addEventListener(eventType, (e) => {
          let el = e.target;
          while (!el.matches(selector)) {
            if (element === el) {
              el = null;
              break;
            }
            el = el.parentNode;
          }
          el && fn.call(el, el);
        });
        return element;
      }
      delegate(document.querySelectorAll("ul")[0], "click", "li", func);
      function func(el) {
        console.log(`el:   ${el.innerHTML}`);
      }

思路是点击 span 后(li 内有其他元素同理),递归遍历 span 的祖先元素看其中有没有 ul 里面的 li。