抽屉重复打开时事件触发次数递增问题成因及解决方法
问题产生原因
核心问题是事件监听器重复绑定且未被销毁:
- 监听
.heading-test点击的逻辑写在了showmodal函数内部,每次调用函数打开抽屉,都会往document上新增一个完全独立的点击事件回调。 - 关闭抽屉时仅用
innerHTML = ''删除了DOM节点,这个操作不会清除之前绑定在document上的事件监听器,所有历史绑定的回调都会一直驻留在内存中。 - 页面触发点击事件时,所有绑定过的符合条件的回调会依次执行,所以每多打开一次抽屉,点击标题时控制台的打印次数就会+1,触发次数和抽屉累计打开次数完全对应。
- 另外两个监听打开按钮、关闭按钮的逻辑写在函数最外层,页面加载时只会绑定1次,因此不会出现重复触发的问题。
修复方案
方案1(推荐):将标题点击监听移到函数外,仅初始化绑定1次
原有写法本身用的是全局事件委托逻辑,不需要在每次生成DOM的时候重复绑定,页面加载时绑定一次就可以覆盖所有动态生成的.heading-test元素,性能最优。
修复后完整JS代码如下,原有CSS、HTML代码无需改动:
const test_modal = document.querySelector('.test-modal'); // 打开按钮监听:页面初始化仅绑定1次 document.addEventListener('click', (e) => { if(e.target.classList.contains('btn')) { e.preventDefault(); const value = e.target.closest('.btn').getAttribute("data-name"); showmodal(value); } }) // 标题点击监听:移到函数外,页面初始化仅绑定1次 document.addEventListener('click', (e) => { if(e.target.classList.contains('heading-test')) { console.log(e.target); } }) // 关闭按钮监听:页面初始化仅绑定1次 document.addEventListener('click', (e) => { if(e.target.classList.contains('closeme')) { test_modal.innerHTML = ``; } }) function showmodal(value) { test_modal.innerHTML = ` <div class="inner-test"><h3 class="heading-test">${value}</h3><button class="closeme">Close</button></div> `; }
方案2:函数内绑定前先移除旧监听器
如果业务场景必须把绑定逻辑写在showmodal内部,注意不能用匿名函数作为回调(移除事件需要传入和绑定时完全一致的函数引用),需要把回调抽为命名函数,每次绑定前先移除旧的同引用监听器:
// 抽离命名回调函数 function headingClickHandler(e) { if(e.target.classList.contains('heading-test')) { console.log(e.target); } } function showmodal(value) { test_modal.innerHTML = ` <div class="inner-test"><h3 class="heading-test">${value}</h3><button class="closeme">Close</button></div> `; // 绑定前先清除之前可能存在的同引用监听器 document.removeEventListener('click', headingClickHandler); document.addEventListener('click', headingClickHandler); }
方案3:直接给动态生成的元素绑定事件
不使用全局委托,在生成抽屉DOM后,直接给当前的标题元素绑定事件,关闭抽屉用innerHTML清空内容时,DOM节点被移除,对应的监听器也会被自动销毁,不会残留:
function showmodal(value) { test_modal.innerHTML = ` <div class="inner-test"><h3 class="heading-test">${value}</h3><button class="closeme">Close</button></div> `; // 直接给当前生成的标题节点绑定事件 test_modal.querySelector('.heading-test').addEventListener('click', (e) => { console.log(e.target); }) }
内容的提问来源于stack exchange,提问作者Leith
相关产品推荐
相关产品推荐

