如何控制 Window 对象 addEventListener 的事件监听器执行顺序
在 Web 开发中,我们经常需要给 window 对象添加多个事件监听器,例如监听 load、resize、scroll 等事件。默认情况下,使用 addEventListener 注册的监听器会按照注册的顺序依次执行,但实际开发中可能会遇到需要调整执行顺序的场景,本文将详细介绍相关的实现方法和注意事项。
默认执行顺序规则
根据 DOM 事件规范,addEventListener 注册的监听器在执行时会遵循以下默认规则:
相同事件类型、相同捕获/冒泡阶段的监听器,按照注册顺序依次执行
捕获阶段的监听器优先于冒泡阶段的监听器执行
如果监听器注册了 once 选项,执行一次后会自动移除,不会影响后续顺序
我们可以通过以下示例验证默认顺序:
// 监听 window 的 click 事件,默认冒泡阶段
window.addEventListener('click', function() {
console.log('第一个监听器执行');
});
window.addEventListener('click', function() {
console.log('第二个监听器执行');
});
// 点击 window 后控制台输出顺序为:
// 第一个监听器执行
// 第二个监听器执行调整执行顺序的方法
方法一:调整注册顺序
最直接的方式是按照期望的执行顺序依次注册监听器,优先级高的监听器先注册。这种方式适用于可以在初始化阶段明确所有监听器顺序的场景。
// 期望先执行 highPriority 函数,再执行 lowPriority 函数
function highPriority() {
console.log('高优先级监听器执行');
}
function lowPriority() {
console.log('低优先级监听器执行');
}
// 先注册高优先级监听器
window.addEventListener('load', highPriority);
// 再注册低优先级监听器
window.addEventListener('load', lowPriority);方法二:利用捕获/冒泡阶段差异
addEventListener 的第三个参数可以指定监听器是在捕获阶段还是冒泡阶段执行,捕获阶段的监听器会比冒泡阶段的监听器先执行。我们可以将需要优先执行的监听器放在捕获阶段,其他的放在冒泡阶段。
// 捕获阶段的监听器,优先执行
window.addEventListener('resize', function() {
console.log('捕获阶段监听器执行');
}, true);
// 冒泡阶段的监听器,后执行(第三个参数默认是 false)
window.addEventListener('resize', function() {
console.log('冒泡阶段监听器执行');
});
// 触发 resize 事件后输出顺序:
// 捕获阶段监听器执行
// 冒泡阶段监听器执行方法三:移除后重新注册
如果监听器已经注册,需要先调整执行顺序,可以先移除原有监听器,再按照新的顺序重新注册。这种方式需要注意保留监听器的引用,避免无法移除的情况。
function listenerA() {
console.log('监听器A执行');
}
function listenerB() {
console.log('监听器B执行');
}
// 初始注册顺序:A 先于 B
window.addEventListener('scroll', listenerA);
window.addEventListener('scroll', listenerB);
// 需要调整顺序为 B 先于 A,先移除原有监听器
window.removeEventListener('scroll', listenerA);
window.removeEventListener('scroll', listenerB);
// 按照新顺序重新注册:B 先于 A
window.addEventListener('scroll', listenerB);
window.addEventListener('scroll', listenerA);方法四:通过事件对象控制流程
可以在高优先级的监听器中通过事件对象的方法,控制后续监听器的执行。例如使用 stopImmediatePropagation 方法,会阻止当前事件剩余的监听器执行。
window.addEventListener('click', function(e) {
console.log('第一个监听器执行');
// 阻止后续监听器执行
e.stopImmediatePropagation();
});
window.addEventListener('click', function() {
console.log('第二个监听器执行');
});
// 点击 window 后只会输出:第一个监听器执行如果需要条件性控制,可以在高优先级监听器中根据逻辑判断是否阻止后续执行:
window.addEventListener('load', function(e) {
const needSkip = true; // 实际场景中的判断条件
if (needSkip) {
console.log('条件满足,跳过后续监听器');
e.stopImmediatePropagation();
} else {
console.log('条件不满足,继续执行后续监听器');
}
});
window.addEventListener('load', function() {
console.log('后续监听器执行');
});注意事项
使用 removeEventListener 移除监听器时,必须传入和注册时完全相同的函数引用,匿名函数无法被移除
stopImmediatePropagation 不仅会阻止当前对象的剩余监听器,还会阻止事件的冒泡/捕获传播,使用时需要明确是否需要这个效果
如果监听器注册了 once 选项,执行一次后会自动移除,不需要手动调用 removeEventListener
不同事件类型的监听器互不影响,只有相同事件类型的监听器才会按照上述规则排序
总结
控制 window 对象 addEventListener 的事件监听器执行顺序,最常用的方式是调整注册顺序,或者利用捕获/冒泡阶段的差异。如果已经注册了监听器,可以通过移除后重新注册的方式调整顺序,也可以通过事件对象的 stopImmediatePropagation 方法条件性控制后续监听器的执行。实际开发中可以根据场景选择合适的方式,确保事件逻辑按照预期执行。
addEventListener执行顺序 事件监听器顺序控制 捕获与冒泡阶段 stopImmediatePropagation removeEventListener