列表更新后Event Listener失效、数组删除异常的原因解析
问题1:列表更新后删除按钮事件监听失效
原因分析
页面初始化时,你通过document.querySelectorAll(".close").forEach(...)给初始生成的删除按钮绑定了点击事件,但在updateList函数中,执行document.querySelector("#itemList").innerHTML = ""会把原来的所有列表元素完全销毁,包括它们身上绑定的事件监听器。
之后调用addtoList重新生成列表元素,但addtoList函数里只给<li>绑定了checkedToggle事件,没有给新生成的.close按钮绑定deleteandNone事件。所以新元素的删除按钮自然没有点击响应,你后来在updateList里重新绑定事件,本质是给新生成的.close补上了事件监听,才让功能恢复。
优化方案:事件委托(更简洁的写法)
不用每次生成元素都手动绑定事件,可以把事件监听器绑定在父元素<ul id="itemList">上,利用事件冒泡机制处理所有子元素的点击:
// 替换原来的两次事件绑定代码,只需要写一次 document.querySelector("#itemList").addEventListener("click", function(e) { // 判断点击的是删除按钮 if (e.target.classList.contains("close")) { deleteandNone.call(e.target); } // 判断点击的是列表项(切换勾选) else if (e.target.tagName === "LI") { checkedToggle.call(e.target); } });
这样不管列表怎么更新(新增、删除、重置),都不用再手动给元素绑定事件,代码更简洁,也避免了重复绑定的问题。
问题2:删除元素时数组总是删除最后一个元素
原因分析
你创建列表项时,给<li>添加了一个.close的<span>,内容是\u00D7(也就是×符号)。所以<li>的innerText实际上是商品名 + ×,比如"bread×",而你的数组groceryList里存的是纯商品名"bread"。
当你用let item = li.innerText;去数组里找indexOf(item)时,找不到匹配的元素,indexOf会返回-1。而groceryList.splice(-1, 1)的作用是删除数组的最后一个元素,所以就出现了“列表里删了选中项,但数组删了最后一项”的问题。
slice(0, -1)的作用是截取字符串从开头到倒数第二个字符,刚好把末尾的×去掉,得到了和数组里一致的纯商品名,这时indexOf就能正确找到对应的索引,splice也就删掉了数组里对应的元素。
更可靠的方案:给元素存数据标识
依赖文本截取容易出问题(比如商品名本身包含×的情况),可以给<li>元素添加自定义属性存储商品名:
// 在addtoList函数里创建li时,添加data属性 function addtoList() { groceryList.forEach((item) => { let li = document.createElement("li"); li.innerText = item; // 存储商品名到自定义属性 li.dataset.item = item; list.appendChild(li); let span = document.createElement("span"); li.append(span); span.classList.add("close"); span.innerText = "\u00D7"; li.addEventListener("click", checkedToggle, false); }); } // 修改deleteandNone函数,直接取data属性的值 const deleteandNone = function () { let li = event.target.parentNode; let item = li.dataset.item; removefromArrayOnly(item); li.style.display = "none"; };
这样就不用依赖文本内容,逻辑更稳定。
内容的提问来源于stack exchange,提问作者AlexSolo

