首先来例子:给列表里面的三个元素绑定事件,点击它们的时候弹出各自元素里的数字。
了解了如何给页面上的元素绑定事件之后,只需要遍历节点,依次绑定就可以了:
<html>
<body>
<ul id="container">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
function clickHandler(e) {
alert(e.target.innerHTML)
}
var nodeList = document.querySelectorAll('li')
nodeList.forEach(function(item) {
item.addEventListener('click', clickHandler)
})
</script>
</body>
</html>
复制代码
现在如果将列表元素的数量扩展到更多,或者是动态的无限个。例如:当我们刷微博的时候,给微博点赞,下拉会持续的加载更多条微博。这个时候如果还用上面的方式来绑定的话,既影响性能,也不优雅。于是就有了事件委托:
<html>
<body>
<ul id="container">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
function clickHandler(e) {
alert(e.target.innerHTML)
}
var container = document.querySelector('#container')
container.addEventListener('click', clickHandler)
</script>
</body>
</html>
复制代码
将事件绑定到列表的外层容器上,当我们点击容器里的所有元素(包括容器本身),都会触发点击事件,我们只需要绑定一次,就可以处理里面所有元素的对应事件,这就叫事件委托。
测试一下上面这个例子,会发现,点击容器的时候,也会响应事件,与我们预期不符,只需要在事件处理时做一点过滤就可以了:
<html>
<body>
<ul id="container">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
function clickHandler(e) {
if (e.target.tagName.toLocaleLowerCase() === 'li') {
alert(e.target.innerHTML)
}
}
var container = document.querySelector('#container')
container.addEventListener('click', clickHandler)
</script>
</body>
</html>
复制代码
只有在点击到li节点的时候才响应。由于上面的例子过于简单,没有办法完全暴露出实际场景中遇到的问题,不过解决起来的思路是一样的。
试试JQuery提供事件委托的方法:
<html>
<body>
<ul id="container">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script>
<script>
function clickHandler(e) {
alert(e.target.innerHTML)
}
$('#container').on('click', 'li', clickHandler)
</script>
</body>
</html>
复制代码
至此,已经可以愉快的使用事件委托来处理类似的事件绑定场景了。但是还没有结束,事件委托是基于DOM事件冒泡的机制来实现的。我们还需要了解一下事件冒泡,因为这个是考点...
我在网上盗了一张图来说明事件捕获和事件冒泡。(怎么又多了一个事件捕获?这么理解就可以了:事件捕获是在绑定事件的时候可选的另一种事件触发机制。)
这里我们只管事件冒泡,图说很直白了,就是点击事件触发后,会从最底层的元素,依次向上触发父元素的点击事件~
冒泡过程当然也是可以阻止的,如果有需要的话。
最后,增加一点难度,给要代理的节点添加一些子节点,这样就没办法直接通过事件触发的节点来,实现了一个简单版本的事件委托函数:
<html>
<body>
<ul id="container">
<li data="1"><div>1</div><div>3</div></li>
<li data="2"><div>2</div><div>2</div></li>
<li data="3"><div>3</div><div>1</div></li>
</ul>
<script>
function clickHandler(e) {
// DOM结构变得复杂,通过li节点上的data属性来标识
alert(e.realTarget.getAttribute('data'))
}
// 获取到事件触发的最底层节点后,需要向上找需要委托的节点,这里通过tagName来标识
function findTarget(el, tagName, container) {
// 最外层直到事件委托的容器,如果没有找到需要委托的节点就放弃
if (el == container) {
return null
}
if (el.tagName.toLocaleLowerCase() === tagName) {
return el
}
return findTarget(el.parentNode, tagName, container)
}
function eventDelegate(container, type, tagName, handler) {
container.addEventListener(type, function(e){
var target = findTarget(e.target, tagName, container)
if (target != null) {
e.realTarget = target
handler(e)
}
})
}
eventDelegate(document.querySelector('#container'), 'click', 'li', clickHandler)
</script>
</body>
</html>
复制代码
小结
事件委托应该属于事件绑定时的一个优化方案。当我们写简单demo的时候,通常不需要考虑,但是实际场景中会经常用到~