场景:
有个列表 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。