问题
在前端开发中,我们经常需要为动态创建的元素绑定事件,然而一个诡异的现象是,我们明明绑定了事件但是却没有正常触发。
在 jQuery 中,我们常常通过以下方式将事件直接绑定到元素上:
$('.submit-btn').on('click', function() {
// TO DO ...
})
这种方式对静态元素是有效的,而对于动态元素,这种直接绑定事件的方法是无效的。
原因
核心原因是 jQuery 的直接绑定方法 $('selector').on(eventType, handler) 只对脚本执行时 DOM 中已存在的元素生效(也就是我们直接在 HTML 里写的元素),而对于动态元素,页面加载时它们还没有被创建,选择器无法选择到这些元素,自然无法将事件绑定上去。
解决方法
对于动态元素事件绑定失效问题,典型的解决方法是使用事件委托。事件委托的原理是将事件绑定到一个静态的、已经存在于 DOM 的父元素(或祖先元素)上,当事件在子元素上触发时,由于事件冒泡机制,事件会一路向上冒泡到这个绑定了处理程序的父元素上,若触发事件的子元素与父元素绑定的子元素选择器匹配,则执行事件处理程序。
事件委托的语法如下。其中若 childSelector 为空,则是直接绑定,当事件发生在 parentSelector 上或从其子元素冒泡到其上时则会触发处理程序;若 childSelector 不为空,则是事件委托,当事件直接在 parentSelector 上发生时,处理程序不会被执行,只有当事件发生在与 childSelector 匹配的后代元素上时,才会调用处理程序。
$(parentSelector).on(eventType, childSelector, function(event) {
// 事件处理程序
});
我们通过以下示例进行理解:
<body>
<div class="container">
<button id="btn-1">按钮 1</button>
<button id="btn-2">按钮 2</button>
</div>
<script>
$(document).on('click', '#btn-1', function() {
console.log('Button 1 is clicked.');
});
</script>
</body>
我们在 document(页面最顶层的容器)上委托处理 #btn-1 的 click 事件。当我们点击 #btn-1 时,事件经 .container、body... 一直向上冒泡到 document,然后 jQuery 判断触发事件的 target 是否匹配绑定的选择器 #btn-1,若匹配,则执行回调函数。同理,当我们点击 #btn-2 时,事件同样冒泡上来,但是由于事件发生在 #btn-2 上,与绑定的选择器 #btn-1 不匹配,于是回调函数不会被执行。
通过事件委托,由于事件实际是绑定在已存在的父元素上,故不会出现因为元素不存在而绑定失败的情况。无论元素是硬编码的还是动态创建的,只要它被点击,事件就会冒泡到上层元素,并由上层元素上的委托处理器捕获并执行。
以下是一个动态元素的例子。这里同时有直接绑定和事件委托两种事件绑定方式。对于 .list,它是 HTML 中硬编码的元素,页面加载时即存在,故我们无需事件委托,直接给它绑定事件即可;对于 .list 下的 li,它们是在点击按钮后动态添加到列表中的,页面加载时尚不存在,因此,若我们仍采用直接绑定的方式,必然是绑定不上的,我们需要将事件委托到某个上层元素上,这里我们直接委托给 .list,当然我们也可以仍然委托给 document 或是 body 等其他上层元素,不过我们尽量选择离目标元素最近的父元素,减少无效冒泡,提高性能。
<body>
<ul class="list"></ul>
<button id="add-item">Add Item</button>
<script>
$(function() {
$('#add-item').on('click', function() {
$('.list').append('<li>This is an item.</li>');
});
$('.list').on('click', 'li', function() {
alert('You clicked an item.');
});
});
</script>
</body>
总结
两种方式对比
$('selector').on:将事件直接绑定到元素上,只对静态元素有效,动态加载的元素无法直接绑定。$(document).on:将事件绑定到稳定的父元素上进行委托,事件冒泡上来后 jQuery 判断目标是否匹配,决定是否执行,对动态/静态元素均有效。
注意事项
- 父元素选择:尽量选择离目标元素最近的稳定父元素。前文的
$(document).on只是用以作为事件委托的典型代表,并不是说一定要绑定在document上,优先绑定到最近的稳定父元素上可以减少不必要的事件冒泡开销。 - 其他场景:除了动态元素场景外,事件委托在有大量子元素的场景下也更为高效。例如,一个
ul列表中有 1000 个li子元素,当使用$('ul li').on直接绑定时,实际上是为每个子元素都创建了一个事件监听器,不仅会占用大量内存,还需要消耗大量计算时间。而通过事件委托,我们只需要在父元素上挂载 1 个事件监听器,省去了大量创建监听函数的时间和内存空间,因此,当子元素数量越多,事件委托带来的性能提升就越明显。
Comments 4 条评论
学长是神来的
@夜华曦 学弟不要硼砂我了
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊大佬
@2864625260
