事件捕获、事件冒泡、事件委托,这三个相似又不尽相同的术语把我搞懵了很长一段时间,今天专门抽时间挨个看了一遍。
首先,是那个闻名遐迩的图
事件捕获和事件冒泡是事件流机制层面的东西,不以代码的意志为转移。
DOM2级规范(浏览器自身的事件规范)要求事件应该从document对象开始向下传播,找到具体的目标前,整个过程都是捕获阶段。
找到具体的目标后,开始向外层冒泡,直到回到document对象结束,这个过程叫冒泡阶段。
但规范只是规范,较旧的浏览器都是从window对象开始捕获事件的,建议放心地使用事件冒泡,特殊情况下再考虑事件捕获。
接下来是事件委托,这个才是重点!
JS和HTML之间是靠事件来实现交互的,如果有多个div需要绑定click事件监听,无论是DOM0级和DOM2级,添加到页面上的事件处理程序数量将直接关系到页面的整体性能。
- 首先,每个函数都是对象,都会占内存。内存中的对象越多,性能就越差。
- 其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。也就是说在多个节点上挂载事件,会影响DOM的加载。
事件委托就是解决性能问题的,你可以把它看做是一种解决方案或设计模式。
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
少废话,上代码(以下例子来自《Javascript高级程序设计》,略删改)
// 面对这样一段HTML
<ul id="myLinks">
<li id="goSomewhere"></li>
<li id="doSomething"></li>
<li id="sayHi"></li>
</ul>
不使用事件委托的笨办法:
var item1 = document.getElementById('goSomewhere')
var item2 = document.getElementById('doSomething')
var item3 = document.getElementById('sayHi')
item1.addEventListener('click', function(e) {
}, false)
item2.addEventListener('click', function(e) {
}, false)
item3.addEventListener('click', function(e) {
}, false)
使用事件委托,对于用户来说最终的结果相同,但我们其实只取了一个DOM元素,只添加了一个事件处理程序,但这种实现方案所需要占用的内存更少。
var list = document.getElementById('myLinks')
list.addEventListener('click', function(e) {
var target = e.target;
switch(target.id) {
case 'goSomewhere':
// TODO
break;
case 'doSomething':
// TODO
break;
case 'sayHi':
// TODO
break;
}
}, false)
如果可行的话,可以考虑为document对象添加一个事件处理程序,优点如下:
- document对象在DOM树渲染的初期就已经准备完毕,无需等待DOMContentLoaded或load事件,而且document对象不像其他DOM节点那样会被移除。换句话说,只要目标元素呈现在页面上,就已经具备了事件处理程序。
- 由于只引用一个DOM对象用来添加事件处理程序,在页面中设置事件处理程序所需的时间更少。
- 整个页面占用的内存更少,提升整体性能。
关于上面三点的最后一点,解释一下:如果我们没有使用事件委托,那在具体元素比如某div上添加的监听,在该div被移除出DOM树后,绑在它身上的事件处理程序可能无法被垃圾回收,造成浪费。
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JS代码之间会建立一个连接,这种连接越多,页面执行效率越低,利用事件委托可以尽可能减少这种连接数量。呃……虽然可能也没法成功垃圾回收,但整体性能还是更高的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。