一、捕获与冒泡模式
在讨论addEventListener第三个参数之前,先来了解一下事件流,也就是事件的传播方式。
事件流分为两种:捕获模式和冒泡模式。在捕获模式中,事件从最外层的节点开始传播,一直传递到最底层的节点;而冒泡模式则恰好相反,从最底层的节点开始传播,逐渐向外传递,到最后传递到最外层的节点。
浏览器对事件流的处理分为三个阶段:
1. 捕获阶段
2. 目标阶段
3. 冒泡阶段
捕获阶段是从document对象到触发事件的最外层元素,目标阶段是事件传播到目标元素,冒泡阶段是从目标元素到最外层元素。
在addEventListener中,第三个参数就是用来指定事件模式的,默认值为false,表示使用冒泡模式。如果将其设置为true,则表示使用捕获模式。
// 冒泡模式
document.getElementById('example').addEventListener('click', function() {
console.log('我是来自 example 的点击事件!');
});
// 捕获模式
document.getElementById('example').addEventListener('click', function() {
console.log('我是来自 example 的点击事件!');
}, true);
二、事件委托
事件委托是指利用事件机制,将一个父元素的事件处理程序注册到子元素中,使得子元素在触发事件时,可以执行父元素的事件处理程序。
这种编程方式的好处在于,可以减少事件程序的数量,提高页面性能,并且可以方便地动态添加或删除子元素,而不需要重新绑定事件处理程序。
实现事件委托的关键在于,可以通过事件传播机制,将子元素的事件传递到父元素上。在这个过程中,可以通过第三个参数来控制事件的传播方式。
以ul元素为父元素,li元素为子元素为例:
// 普通方式注册事件处理程序
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
lis[i].addEventListener('click', function() {
console.log('我是来自' + this.innerText + '的点击事件!');
});
}
// 事件委托方式注册事件处理程序
document.querySelector('ul').addEventListener('click', function(event) {
if (event.target.tagName.toLowerCase() === 'li') {
console.log('我是来自' + event.target.innerText + '的点击事件!');
}
});
可以看到,事件委托可以减少事件处理程序的数量,也可以方便地动态添加或删除子元素。
三、事件绑定与解绑
addEventListener除了可以进行事件注册外,也可以通过removeEventListener方法来进行解绑。removeEventListener需要传入相同的事件类型、回调函数以及可选的useCapture参数,才能解绑之前相同的listener。
var triggerBtn = document.getElementById('trigger');
function clickEvent() {
console.log('按钮被点击了!');
}
// 绑定事件处理程序
triggerBtn.addEventListener('click', clickEvent);
// 解绑事件处理程序
triggerBtn.removeEventListener('click', clickEvent);
四、一次性事件绑定
有时候我们需要在某个事件触发后,执行完事件处理程序后,解除事件绑定。由于removeEventListener需要了解之前绑定的listener函数,所以无法实现一次性解绑。不过可以借助匿名函数实现一次性事件绑定。
// 绑定事件处理程序
document.getElementById('example').addEventListener('click', function() {
console.log('我是一次性的事件处理程序!');
document.getElementById('example').removeEventListener('click', arguments.callee);
});
在回调函数内部使用arguments.callee,指向了该函数本身,从而达到了一次性的效果。
五、多次事件触发的节流和防抖
事件节流和防抖都是为了解决事件频繁触发而导致性能问题的问题,事件节流和防抖的本质是一样的,都是通过降低事件触发频率,从而提升性能。
5.1 节流
事件节流的本质是使用setTimeout,实现“定时触发”。它的实现方式是,第一次触发事件立即执行事件处理程序,然后在一定时间内不管事件触发了多少次,都不再执行事件处理程序,直到时间到了之后,才再次执行事件处理程序。
function throttle(fn, delay) {
let timer = null;
return function() {
let args = arguments;
let that = this;
if (!timer) {
timer = setTimeout(function() {
fn.apply(that, args);
timer = null;
}, delay);
}
};
}
// 用法
document.getElementById('example').addEventListener('click', throttle(function() {
console.log('我是通过节流方式处理的点击事件!');
}, 1000));
5.2 防抖
事件防抖的本质是使用setTimeout,实现“延迟触发”。它的实现方式是,事件触发后,等待一定时间,如果在这段时间内没有再次触发事件,就执行事件处理程序,否则,重新开始计时。
function debounce(fn, delay) {
let timer = null;
return function() {
let args = arguments;
let that = this;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
fn.apply(that, args);
timer = null;
}, delay);
};
}
// 用法
document.getElementById('example').addEventListener('click', debounce(function() {
console.log('我是通过防抖方式处理的点击事件!');
}, 1000));
六、总结
在日常的开发工作中,我们经常需要为元素添加各种事件处理程序,而利用addEventListener提供的第三个参数,可以实现捕获和冒泡模式的控制、事件委托、事件绑定和解绑、一次性事件绑定、事件节流和防抖等功能,实现更灵活、高效的事件管理。