在循环中单击另一个元素时将事件侦听器添加到元素

Posted

技术标签:

【中文标题】在循环中单击另一个元素时将事件侦听器添加到元素【英文标题】:Adding event Listener to elements on click of another in loop 【发布时间】:2021-02-17 14:06:15 【问题描述】:

我的打字稿项目有一个非常烦人的问题。没什么可看中的,但由于某种原因,一旦我单击这 3 个菜单中的任何一个中的一个元素,onclick 的执行次数与特定菜单中的元素数量一样多,而不是每次单击一次。

我已经尝试了一段时间,因此在 cmets 或不同的添加侦听器中,功能有所不同。

如何让这些事件监听器只执行一次在菜单中选择一个选项

onst menu1 = document.getElementById("menu1") as htmlDivElement;
const menu2 = document.getElementById("menu2") as HTMLDivElement;
const menu3 = document.getElementById("menu3") as HTMLDivElement;



menu1.addEventListener("click",  function () 

  let elements = menu1.querySelectorAll("input");
  for(let i=0; i< elements.length; i++) 
      elements[i].addEventListener("click", function () 
        if (elements[i].checked) 
          let val = elements[i].value;
          dataSet.setEffect(elements[i].value);
          console.log(dataSet);
        
      );
    
);


menu2.addEventListener("click", function()

  const elements2 = menu1.querySelectorAll("label");
  // for (let element of elements2) 
  //   element.addEventListener("click", function () 
  //       dataSet.setEffect(element.value);
  //       console.log(dataSet);
  //   );
  // 
  elements2.forEach( function (ele,index) 
      // ele.addEventListener("click", function () 
      //         dataSet.setEffect(ele.innerText);
      //         console.log(dataSet);
      // );
  ele.onclick = function () 
    dataSet.setEffect(ele.innerText);
    console.log(dataSet,ele);
  
  )

);


menu3.addEventListener("click", ()=>
  let elements3 = menu3.children;
  for(let i=0; i< elements3.length; i++)
  
    elements3[i].addEventListener("click", ()=>
      dataSet.setBGC(elements3[i].id);
      console.log(dataSet);
    );
  
);

单个菜单的html

 <div class="container">
        <div class="header-text">
            <h2>Effect</h2>
        </div>
        <div class="select" id="menu1" tabindex="1">
            <input class="options-select"  value="solidColor" name="selectors1" type="radio" id="opt1" checked>
                <label for="opt1" class="option">Solid Color</label>
            <input class="options-select" value="EQCenter" name="selectors1" type="radio" id="opt2">
                <label for="opt2" class="option">EQ Center</label>
            <input class="options-select" value="Strobe" name="selectors1" type="radio" id="opt3">
                <label for="opt3" class="option">Strobe</label>
            <input class="options-select" value="Sparkel" name="selectors1" type="radio" id="opt4">
                <label for="opt4" class="option">Sparkel</label>
            <input class="options-select" value="swicth" name="selectors1" type="radio" id="opt5">
                <label for="opt5" class="option">Switch</label>
        </div>
        
    </div>

【问题讨论】:

addEventListener 追加,因此每次单击时都会将另一个侦听器追加到列表中。 【参考方案1】:

问题是事件propagation 之一(也称为“冒泡”,如链接的 MDN 文章中所示)。每次点击inputs 之一时,它也会触发每个父元素上的事件。由于这包括菜单本身,因此您的事件处理程序将再次运行 - 为每个项目添加更多事件侦听器。

解决这个问题的最简单的代码更改就是使用事件对象的内置stopPropagation 方法来防止这种情况:

  elements[i].addEventListener("click", function (event) 
    event.stopPropagation();
    if (elements[i].checked) 
      let val = elements[i].value;
      dataSet.setEffect(elements[i].value);
      console.log(dataSet);
    
  );

但是,虽然这应该可以解决问题,但我强烈建议您考虑不同的方法来实现您的目标。在另一个事件侦听器中添加事件侦听器基本上是一种反模式 - 它很容易导致像您在这里遇到的问题,而且我正在努力考虑任何需要或有用的情况。由于您的输入就在 HTML 源代码中,而不是使用 javascript 动态添加,因此我看不出有什么理由不能在页面加载时直接将事件侦听器添加到每个输入。这将导致代码更简洁。

【讨论】:

【参考方案2】:

你写道:

每次点击menu1,然后将一个新的 onClick 监听器附加到他的每个孩子。

menu1.addEventListener("click",  function () 
  let elements = menu1.querySelectorAll("input");
  for(let i=0; i< elements.length; i++) 
      elements[i].addEventListener("click", function () 
        if (elements[i].checked) 
          let val = elements[i].value;
          dataSet.setEffect(elements[i].value);
          console.log(dataSet);
        
      );
    
);

替换为

let elements = menu1.querySelectorAll("input");
for(let i=0; i< elements.length; i++) 
  let elem = elements[i]; //To avoid the common closure problem
  elem.addEventListener("click", function () 
    if (elem.checked) 
      let val = elem.value;
      dataSet.setEffect(elem.value);
      console.log(dataSet);
    
   );

【讨论】:

以上是关于在循环中单击另一个元素时将事件侦听器添加到元素的主要内容,如果未能解决你的问题,请参考以下文章

单击侦听器事件不会在 SVG 元素上触发(字体真棒图标)

为啥事件监听器只在 for 循环中创建的最后一个元素上注册? [复制]

是否可以将事件侦听器绑定到来自外部脚本的影子 dom 内的元素?

将事件侦听器添加到新添加的DOM元素

如何使用事件侦听器向对象数组添加新元素并将其显示在 html 页面上

如何为 RecyclerView 列表元素设置单击侦听器以显示 AlertDialog?