由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父结点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫事件委托。
事件委托的优点
-
减少内存消耗,提高性能
对于如下代码
<div class='buttons'> <button>按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> </div>现需要在点击每个 button 时响应一个事件,如果给每个 button 都绑定一个函数,那么会消耗较多的内存。借助事件委托,只需要将监听函数定义在父元素 div 上,这样不管点击的是哪一个 button 标签,遵循冒泡的传递机制,都会响应事件。
-
可以监听动态元素
很多时候,用户的一些操作会增删某个子元素,如果一开始给每个子元素绑定事件,那么在子元素发生变化是,就需要给新增元素绑定事件,给要删除的元素解绑事件,使用事件委托就会省去很多类似的麻烦。
事件委托的实现:
<div id='container'>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
</div>
<script>
container.addEventListener('click', function(e){
//把目标元素赋值给t
let t = e.target
//判断是否匹配目标元素
if (t.matchs('button')) {
console.log('这是:' + t.textContent);
}
};
</script>
这样点击 button 时会触发事件,打印出相应元素的文本内容。
基于此,可以封装一个事件委托函数
on('click', '#container', 'button', ()=>{
console.log('这是:' + t.textContent)
})
function on(eventType, element, selector, fn) {
//判断是不是 Element
//如果传进来的是选择器,不是 Element 本身,就先变成 Element
//因为只有 Element 才能监听事件
if (!(element instanceof Element)) {
element = element.querySelector(element)
}
parentElement.addEventListener(eventType, (e)=>{
let target = e.target
if (target.matches(selector)) {
fn(e)
}
})
}
当被点击的元素有多个层级时,则需要递归判断其祖先元素,直到找到监听的元素(注意限定判断范围)
on('click', '#contaniner', 'button', ()=>{
console.log('用户点击了li')
})
function on(eventType, element, selector, fn) {
if (!(element instanceof Element)) {
element = document.querySelector(element)
}
element.addEventListener(eventType, (e)=>{
let el = e.target
// 如果匹配到了selector就跳出循环
while(!el.matches(selector)){
if (el === element){
//已经找到了父元素,说明还没找到,就设置为null
el = null
break
}
el = el.parentNode
}
// 找到了el, 就调用函数
el && fn.call(el, e, el)
})
}