JavaScript addEventListener重复触发问题原因咨询
点击按钮后绑定的body点击事件触发两次的原因
我想实现点击按钮打开下拉菜单,点击DOM任意区域(下拉菜单除外)时关闭它,但当前代码运行时点击按钮会触发两次fire输出,想知道原因。
核心代码如下:
<button id="btn">ABC</button>
document.getElementById("btn").addEventListener("click", () => { console.log("fire"); document.getElementsByTagName("body")[0].addEventListener("click", () => { console.log("fire"); }); });
注:已忽略监听器累积问题,为简化代码移除了相关逻辑
原因解析
这是事件冒泡机制导致的:
- 当你点击按钮时,按钮自身的
click事件先触发,输出第一次fire; - 浏览器的事件冒泡会让这个点击事件沿着DOM树向上传递,最终到达
body节点; - 而你刚好在按钮的
click处理函数里给body绑定了新的click监听器——由于事件冒泡是同步执行的,这个刚绑定的监听器会立刻被触发,于是输出第二次fire。
简单说就是:按钮点击触发 → 绑定body监听 → 按钮的点击事件冒泡到body → 新绑定的body监听执行,两次输出就这么来的。
针对下拉菜单需求的修正思路
- 提前绑定body监听+阻止按钮冒泡:不要在按钮点击时才绑定body监听,提前绑定好,同时在按钮点击事件里阻止冒泡,避免触发body的监听(这样点击按钮只会打开菜单,点击其他区域才会触发body监听关闭菜单);
- 给下拉菜单加阻止冒泡:如果有下拉菜单元素,给它的
click事件也加e.stopPropagation(),这样点击菜单内部不会触发body的关闭逻辑; - 不推荐用
setTimeout延迟绑定body监听,虽然能避开冒泡,但代码逻辑不够清晰。
举个实用的修正示例:
const btn = document.getElementById("btn"); const body = document.body; // 假设下拉菜单元素是#dropdown const dropdown = document.getElementById("dropdown"); // 提前绑定body的关闭逻辑 body.addEventListener("click", () => { dropdown.style.display = "none"; }); // 按钮点击打开菜单,同时阻止冒泡 btn.addEventListener("click", (e) => { e.stopPropagation(); dropdown.style.display = "block"; }); // 点击菜单内部不关闭 dropdown.addEventListener("click", (e) => { e.stopPropagation(); });
内容的提问来源于stack exchange,提问作者nocode
相关产品推荐
相关产品推荐

