事件委托就像前台统一代收快递:不是让每栋楼、每个人自己跑下楼(每个小元素绑事件),而是只让前台(父元素)一个人收。点哪个小元素,它的事件会"冒泡"传到前台,前台用 e.target 一看快递单"这是谁的",再办事。好处:少绑事件、以后新来的人也自动有快递可收。
// ❌ 笨办法:每个 li 都绑一次
let lis = document.querySelectorAll('#menu li'); // 先找出所有 li
lis.forEach(li => li.addEventListener('click', show)); // 挨个给每个 li 绑事件
// 问题:以后新加的 li 没绑上,还得再绑一遍,浪费
// ✅ 聪明办法:只给爹 ul 绑一次
let ul = document.getElementById('menu'); // 只抓到爹 ul
ul.addEventListener('click', function (e) { // 点 ul 里任何东西都会冒泡到这
if (e.target.nodeName === 'LI') { // 看快递单:点中的是不是 li
console.log(e.target.innerText); // 是 li,就把它的字打出来
}
});
① 事件会冒泡:点 li,一路冒到爹 ul;② 爹只绑了一个监听;③ 在监听里用 e.target 看一眼"点的是不是 li",是才办事。
如果你忘了判断 e.target 是不是 li 就直接办事,可能点到空白处也触发。因为 ul 里除了 li 还有空格、padding 这些;所以要先看 nodeName === 'LI'(或用 matches)。
因为监听绑在爹 ul 上,新 li 再怎么加,都是 ul 的孩子,点它照样冒泡到 ul——根本不用重新绑。这就是委托最大的好处。
① 用在哪:聊天消息、商品列表、待办项等动态生成的列表点击。
② 常见坑:忘了判断 e.target 是不是目标标签,点空白也触发;新元素忘了绑定。
③ 怎么解决:给父绑一次,用 e.target.matches()/nodeName 判断;新元素自动生效不用重绑。