事件委托

125 阅读2分钟

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件

我委托一个元素帮我监听我本该监听的东西,比如onclick

<ul id="box"> 
    <li>1</li>
    <li>2</li> 
    <li>3</li> 
    <li>4</li> 
</ul>
1.常规思路利用for循环的机制添加
window.onload = function(){
var box = document.getElementById("box");
var li = box.getElementsByTagName('li');
for(var i=0;i<li.length;i++){
li[i].onclick = function(){
alert(123);
}
}
}

如果li标签有100个,那就要循环遍历100次,这样操作DOM次数太多 极大的消耗性能

2.利用事件委托来添加事件
// 监听父元素 ul#box
box.addEventListener('click', (e)=> {
  //通过浏览器传进来的e参数,找到当前点击元素
  const t = e.target
  // 判断当前元素是不是Li标签
  if(t.matches('li') {
    console.log('用户点击了li')
  }
})
  1. 首先监听父元素,
  2. 然后根据浏览器传进去的事件信息,拿到当前点击元素,
  3. 再判断当前点击元素是不是 li 元素, 如果是,就 console.log('用户点击 li 标签')


然后我们可以封装一个事件委托函数

on("click", "#box", "li", () => {
  console.log("用户点击了li");
});

function on(eventType, parentElement, selector, fn) {
  // 先判断是不是element,
  //如果传进来的是选择器,不是element本身,就先变成element,
  // 因为只有element才能监听事件
  if (!(parentElement instanceof Element)) {
    parentElement = parentElement.querySelectorAll(parentElement);
  }
  parentElement.addEventListener(eventType, (e) => {
    let target = e.target;
    if (target.matches(selector)) {
      fn(e);
    }
  });
}

但是以上这种实现有一个小问题,那就是如果被点击元素有多个父元素怎么办?

<ul id="box">
  <li>
    <p>
      <span>1</span>
    </p>
  </li>
  <li>
    <p>
      <span>2</span>
    </p>
  </li>
  <li>
    <p>
      <span>3</span>
    </p>
  </li>
  <li>
    <p>
      <span>4</span>
    </p>
  </li>
</ul>
复制代码

我们需要做的就是:
递归地向上多找几层父节点,直到找到 li 标签,
同时还必须限定,寻找的范围不能超过 parentElement,
拿上面的例子来说,不可以越过 ul 标签,去找 body 标签

on("click", "#box", "li", () => {
  console.log("用户点击了li");
});

function on(eventType, element, selector, fn) {
  if (!(element instanceof Element)) {
    element = document.querySelectorAll(element);
  }

  element.addEventListener(eventType, (e) => {
    let target = e.target;
    // 如果匹配到了selector就跳出循环
    while (!target.matches(selector)) {
      if (target === element) {
        //已经找到了父元素,说明还没找到,就设置为null
        target = null;
        break;
      }
      target = target.parentNode;
    }

    // 找到了target, 就调用函数
    target && fn.call(target, e);
  });
}