事件触发多次

时间:2021-02-22 23:00:35

标签: javascript

我在下面的小代码片段中复制了一个小问题(以最简单的方式,但它仍然显示了我面临的问题)。

这是片段:

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() 获取所有按钮,然后遍历它们并将点击侦听器添加到每个按钮,但我认为它不是最佳的,也不是最佳实践。这就是为什么我来这里询问是否有更好的解决方案,可能是一个将点击侦听器保留在结果框中的解决方案(如果这是最佳解决方案。顺便说一下?)。

因此,即使我找到了解决此问题的方法,有人能告诉我最佳做法和最佳方法是什么吗?

非常感谢您的帮助

1 个答案:

答案 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>