我在下面的小代码片段中复制了一个小问题(以最简单的方式,但它仍然显示了我面临的问题)。
这是片段:
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
dynamicBtnClickListener();
}
function dynamicBtnClickListener() {
resBox.addEventListener('click', function handler(e) {
console.log('You clicked me !');
});
// THE SOLUTION I FOUND FOR THE MOMENT :
//const btns = document.querySelectorAll('button.js-click');
//btns.forEach(btn => {
// btn.addEventListener('click', function handler(e) {
// console.log('You clicked me !');
// });
//});
}
<input type="text" id="search">
<div id="results"></div>
正如您在代码段中看到的,我在输入上有一个第一个侦听器,它会在您输入时生成一个按钮列表。当它为空时,按钮消失。在我的实际案例中,它是一个搜索输入,当用户输入时,调用一个函数,用来自 DB 的结果填充其下方的结果框。
然后我在按钮上有一个点击监听器。在代码片段中,当您单击按钮时,我只是放置了一个控制台('You clicked me')。在我的真实应用中,它获取结果项(每个结果都是一个用户)并将其插入到表格中。
当您打开、关闭然后重新打开结果框时出现问题。这是通过输入一些东西,清除输入,然后重新输入一些东西来完成的。当您执行此操作然后单击其中一个按钮时,它会在您打开/关闭结果框时多次触发它们上的点击事件,因此您将多次在控制台上看到“您点击了我”。
我做了一些研究,其中大部分要求使用 event.stopPropagation() 和/或删除事件侦听器。我确实尝试了所有这些可能的解决方案,尽我所能,但我无法让它发挥作用。
无论如何我找到了解决这个问题的方法(dynamicBtnClickListener() 函数的注释部分),但我觉得它不是最佳的。它包括使用 querySelectorAll() 获取所有按钮,然后遍历它们并将点击侦听器添加到每个按钮,但我认为它不是最佳的,也不是最佳实践。这就是为什么我来这里询问是否有更好的解决方案,可能是一个将点击侦听器保留在结果框中的解决方案(如果这是最佳解决方案。顺便说一下?)。
因此,即使我找到了解决此问题的方法,有人能告诉我最佳做法和最佳方法是什么吗?
非常感谢您的帮助
答案 0 :(得分:3)
每次您在文本区域中键入时,每次都会访问 resBox
并且实际元素 resBox
每次都会获得一个新的事件侦听器(按钮本身没有任何特定的侦听器,所以我让每个按钮都有一个特定的听众单独)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me';
btn.addEventListener('click',function(ev){console.log('You clicked me !')})
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>
现在,这是一个只有一个事件侦听器但可以完全处理这种情况的示例>:D
从技术上讲,这应该更快(因为一个事件侦听器与许多事件侦听器相比),但个人我更喜欢这个选项,因为它“感觉更好”,因为一个函数控制整个按钮布局(这将使它不那么“刚性”)
PS: 速度差别太小了,你可以挑挑拣拣(但如果有一大堆纽扣,是的,这会更好)
const searchBar = document.getElementById('search');
const resBox = document.getElementById('results');
const randChars=()=>{ //random characters to prove ONE event listener can work for multiple buttons in resBox
let arr=["a","b","c","d","e"]
let randIndex=()=>Math.floor(Math.random()*arr.length)||1
let n=randIndex(); let returnChar=""
for(let i=0;i<n;i++){returnChar+=arr[randIndex()]}
return returnChar
}
searchBar.addEventListener('input', function handler(e) {
if (e.target.value === '') {
resBox.innerHTML = '';
return false;
}
setTimeout(() => populate(e), 300);
});
resBox.addEventListener('click',function(ev){ //single event listener for all buttons
let currentButton=ev.path[0]
if(currentButton.tagName!="BUTTON"){return;} //if a button was not clicked
console.log("Button with text\n'"+currentButton.innerText+"'\nwas clicked")
})
function populate(e) {
const btnBox = document.createElement('div');
for (let i = 1; i <= 3; i++) {
const btn = document.createElement('button');
btn.classList.add('js-click')
btn.innerText = 'Click me '+randChars();
btnBox.appendChild(btn);
}
resBox.appendChild(btnBox);
}
<input type="text" id="search">
<div id="results"></div>