javascript - 只有最后一次循环迭代才能成功注册我的点击事件?

标签 javascript loops events scope

我今天阅读了几篇关于将循环生成的变量传递给函数的文章,甚至成功地重现了我看到的一些问题,并正确地实现了这些问题的修复。但是,有一个我似乎无法找到答案。我编写了一个循环,用于将项目添加到下拉列表中,同时它通过 id 为这些项目的 click 事件创建事件监听器。我已验证 id 是唯一的并且没有任何冲突。但是,当我检查我的 Items 数组时,只有最后一项与 onclick 相关联,因此我相信我遇到了另一个范围问题。

class Test {
	Items = [];
	constructor(containerID) {
  	this.ContainerID = containerID;
    this.Container = document.getElementById(containerID);
  }
  AddItems(items) {
  	for (let i = 0; i < items.length; i++) {
    	let itemID = 'li-' + this.ContainerID + '-' + this.Items.length;
      this.Container.innerHTML += '<li id="' + itemID + '">' + items[i] + '</li>';
      
      let item = document.getElementById(itemID);
      item.addEventListener("click", this.OnClick.bind(this, item));
      this.Items.push(item);
    }
  }
  OnClick(item) {
  	alert(item.innerText);
  }
}

var testObject = new Test("test-container");
testObject.AddItems(["Option 1", "Option 2", "Option 3", "Option 4"]);
html, body {
  height: 100%;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
  max-height: 200px;
  overflow-y: auto;
}
ul > li {
  padding: 5px;
  margin: 5px;
  cursor: pointer;
  background-color: #333;
  color: #fff;
}
<ul id="test-container"></ul>

我在这里关注的功能是 AddItemsOnClick。当我将一个项目添加到我的项目集合时,我需要将该项目的 click 事件绑定(bind)到 OnClick 函数,我将在该函数中填充 textbox 使用该项目的 innerText 进行控制。最后一部分很简单,但出于某种原因,我什至无法获得 click 事件来注册这些元素。

我试过:

  • 仅传递元素的 id
  • 传递元素。
  • 使用 var 而不是 let

我尝试过的所有方法都只导致最后一项引发点击事件。我在当前环境之外遇到并解决了同样的问题,没有遇到任何问题。即使是我发现的最流行的解决方案也无法解决这个问题:

  AddItems(items) {
    for (let i = 0; i < items.length; i++) {
        (function(instance, _i) {
            let itemID = 'li-' + instance.ContainerID + '-' + instance.Items.length;
        instance.Container.innerHTML += '<li id="' + itemID + '">' + items[_i] + '</li>';

        let item = document.getElementById(itemID);
        item.addEventListener("click", instance.OnClick.bind(instance, item));
        instance.Items.push(item);
      })(this, i);
    }
  }

这仍然只会导致最后一个项目具有已注册的 click 事件。


更新

奇怪的是,如果我在我的实现代码中对 AddItems 进行二次调用,要添加到 Items 数组的最后一项仍然是唯一具有注册了一个 click 事件。我什至添加了一个 setTimeout 来查看 JavaScript 是否正在合并我的调用,并且仍然得到相同的结果。

var testObject = new Test("test-container");
testObject.AddItems(["Option 1", "Option 2", "Option 3", "Option 4"]);
setTimeout(function() {
    testObject.AddItems(["Option 5", "Option 6", "Option 7", "Option 8"]);
}, 5000);

如何将我的循环范围变量传递给 click 事件的绑定(bind) function

最佳答案

当您连接 innerHTMLContainer的属性(property),您将替换 DOM 元素的全部内容,从而丢失所有已绑定(bind)的事件。当你处理完 innerHTML 时,你要么绑定(bind)事件的 Container (这将是一种解决方法,但仍然很糟糕)或者您只需使用正确的方法:appendChild :

class Test {
  Items = [];
  constructor(containerID) {
    this.ContainerID = containerID;
    this.Container = document.getElementById(containerID);
  }
  AddItems(items) {
    let itemID, item;
    for (let i = 0; i < items.length; i++) {
      itemID = 'li-' + this.ContainerID + '-' + this.Items.length;
      item = document.createElement('li');
      item.id = itemID;
      item.innerHTML = items[i];
      this.Container.appendChild(item);
      item.addEventListener("click", this.OnClick.bind(this, item))
      this.Items.push(item);
    }
  }
  OnClick(item) {
    alert(item.innerText);
  }
}

var testObject = new Test("test-container");
testObject.AddItems(["Option 1", "Option 2", "Option 3", "Option 4"]);
html,
body {
  height: 100%;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
  max-height: 200px;
  overflow-y: auto;
}

ul>li {
  padding: 5px;
  margin: 5px;
  cursor: pointer;
  background-color: #333;
  color: #fff;
}
<ul id="test-container"></ul>


编辑:没有直接关系,但我认为应该提及,出于教育目的:应尽可能避免为每个 child 绑定(bind)一个事件,因为:

  • 它更重(在处理大量 child 时很明显)
  • 如果您动态更新子项,则没有 API 来获取没有特定绑定(bind)的子项;并且忘记在添加新成员后立即绑定(bind)它们是一个容易犯的错误

但是您可以利用事件冒泡:您只在父级上绑定(bind)一个事件。当您进行绑定(bind)时,这甚至适用于 DOM 中不存在的 child 。您更新的示例:

class Test {
  Items = [];
  constructor(id) {
    this.Container = document.getElementById(id);
    this.Container.addEventListener("click", this.OnClick.bind(this));
  }
  AddItems(items) {
    let item;
    for (let i = 0; i < items.length; i++) {
      item = document.createElement("li");
      item.id = `li-${this.Container.id}-${this.Items.length}`;
      item.innerHTML = items[i];
      this.Container.appendChild(item);
      this.Items.push(item);
    }
  }
  OnClick(event) {
    const item = event.target.closest(`#${this.Container.id} li`);
    if (item) {
      alert(item.innerText);
    }
  }
}

const testObject = new Test("test-container");
testObject.AddItems(["Option 1", "Option 2", "Option 3", "Option 4"]);
html,
body {
  height: 100%;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
  max-height: 200px;
  overflow-y: auto;
}

ul>li {
  padding: 5px;
  margin: 5px;
  cursor: pointer;
  background-color: #333;
  color: #fff;
}
<ul id="test-container"></ul>

如您所见,即使绑定(bind)是在 constructor 中完成的,这发生在您添加 <li> 之前s,它可以工作,如果您删除它们并添加其他人,它将继续工作。而且它只是一种绑定(bind),适用于所有现在和 future 的 child 。

请注意,我更改了 OnClick方法,不再覆盖默认参数(即 event )并使用它来选择 <li>调用方法时。

关于javascript - 只有最后一次循环迭代才能成功注册我的点击事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57982511/

相关文章:

javascript - 使用 Javascript、PHP 和 AJAX 输出 SQL 查询

c# - 如何在 C# 中连接到数据库并遍历记录集?

vba - Do While 循环不跳过它应该跳过的文件

javascript - 如何将 jQuery each() 变成常规的 javascript 循环

javascript - 渲染完成时的 Echarts 事件

javascript - PhoneGap ondeviceready 5 秒后未触发

Javascript 连接表格单元格 ASP.NET MVC

javascript - 将支持 node.js 中的原始套接字,例如创建ping数据包?

java - 滚动 Pane 中所有复选框的操作监听器?

javascript - getComputedStyle() 跨浏览器的一致性