为什么我只能在for循环内部使用JavaScript中的getElementsByClassName方法成功找到<ul>下的<li>或<span>标签?

why only inside of the for loop can I successfully find <li> or <span> tag under the <ul> with the getElementsByClassName method in JavaScript?

我正在通过尝试构建一个简单的 TODO LIST 项目来学习使用 truffle 的区块链。 起初,我无法在单击 X 符号时成功禁用待办事项列表中任务项的显示……现在我无意中找到了解决方案,但就是无法弄清楚为什么以及它是如何工作的……

我的解决方法如下代码(特别是底部,从注释开始//点击X,列表消失

我的问题是:

  1. 为什么在这个for循环外(console.log的return为null)查找时用getElementsByClassName方法找不到li,span,close的标签可以使用 ul 标签 ??

  2. 即使我只能在for循环中找到那些标签,为什么这个for循环总是运行等着我点击X??难道for循环不是应该在重复我们要求他们重复的次数后终止吗??!!

    render: function() {
  
       //Load account data
       if(web3.currentProvider.enable){
       //For metamask
         web3.currentProvider.enable().then(function(acc){
            App.account = acc[0];
            $("#accountAddress").html("Your Account: " + App.account);
        });
       }
       else{
         App.account = web3.eth.accounts[0];
         $("#accountAddress").html("Your Account: " + App.account);
       }
    
       // Load contract data
        App.contracts.myTodoList.deployed().then(function(instance) {
          myTodoListInstance = instance;
          return myTodoListInstance.TasksCount();
        }).then(function(TasksCount){

          var shownList = document.getElementById("todo-list")
                  
          TasksCount = TasksCount.toNumber();

          for (var i = 1; i <= TasksCount; i++) {
            myTodoListInstance.Tasks(i).then(function(task) {
                var id = task[0];
                var content = task[1];
                let completed = task[2];

                // create li node inside of for loop so that you won't appendChild the same 'li' over and over again 
                var newTaskNode = document.createElement('li');

                newTaskNode.innerHTML = content;
                
                // Add a "X" symbol in the end of each listed task
                var span = document.createElement("SPAN");
                var txt = document.createTextNode("x");
                span.className = "close";
                span.appendChild(txt);
                newTaskNode.appendChild(span);                

                // Render initial tasks Result
                shownList.appendChild(newTaskNode); 

                // when click to X, list disappear
                //(don't know how it worked... why can't i locate 'li','span','close' outside of this for loop ??)
                //(even i can only locate those tags within this loop, why this for loop always running and waiting for me to click X ??!!)
                var close = document.getElementsByClassName("close");
                var i;
                for (i = 0; i < close.length; i++) {
                  close[i].onclick = function() {
                    var div = this.parentElement;
                    div.style.display = "none";
                  }
                } 
            });             
          } 
        });             
    },

只能访问在 for 循环中创建的元素。

调用render函数后无法立即访问li元素,因为它们尚未创建。列表元素创建需要

的承诺
  1. App.contracts.myTodoList.deployed()
  2. myTodoListInstance.Tasks(i)

先解决

为什么这个 for 循环总是 运行 并等待我点击 X?

[编辑:此答案的先前版本错误地暗示重复设置元素的 onclick 属性 会设置多个点击处理程序。设置 HTML 元素的 onevent 属性会替换以相同方式添加的事件处理程序。]

您可能需要更详细地解释此问题的症状。 for 循环实际上在创建任何关闭和列表元素并将其添加到 DOM 之前完成迭代 - 循环仅向每个 myTodoListInstance.Tasks(i) 承诺添加 then 子句并且不等待处理程序被调用。

添加点击处理程序的代码可以通过替换

来简化(只添加一次处理程序)
var close = document.getElementsByClassName("close");
var i;
for (i = 0; i < close.length; i++) {
  close[i].onclick = function() {
    var div = this.parentElement;
    div.style.display = "none";
  }
}

span.addEventListener( "click", function() {
    var div = this.parentElement;
    div.style.display = "none";
});

点击X,列表消失

我认为这不是问题,因为点击处理程序被编码为将 li 元素的 ul 父级的显示 属性 设置为“none ”。请注意,父项可能不是变量名称所建议的 div 元素。

其他

  • 如果 content 使用 HTML 标记格式化并且已知是安全的,则仅将 newTaskNode.innerHTML 设置为 content;。如果 content 是纯文本,则改为设置 newTaskNode.textContent

如上所示,

  • promise 拒绝没有错误处理,
  • 没有创建在所有列表项和关闭按钮准备就绪之前必须履行的承诺数组。这样的数组可以传递给 Promise.all 以在列表完成时创建一个承诺(如果数组中的任何承诺被拒绝,则被拒绝)
  • Promise 被异步解决。代码中没有任何内容可以保证屏幕上显示的列表项按 i 值的升序排列。最好在 Promise.all 处理程序中创建列表项和关闭按钮,该处理程序以与其 promise 数组参数相同的顺序传递已解析值的数组。