You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何点击事件仅对每隔一个li元素生效?

待办事项列表点击响应异常:原因与解决办法

问题原因

每次点击提交按钮添加新待办时,你都会遍历所有已存在的<li>,给它们重复绑定点击事件。比如:

  • 第一次添加li:给这个li绑定1次点击事件,点击正常切换删除线
  • 第二次添加li:遍历所有2个li,给每个都再绑1次事件。此时第一个li有2个点击事件,点击时会先触发加删除线,紧接着触发取消删除线,看起来就像没反应;而新添加的第二个li只有1次绑定,所以能正常工作
  • 以此类推,旧li的事件绑定次数会越来越多,点击时多次触发反向操作,表现为“每隔一个li没反应”

另外还有个小问题:点击<li>里的删除按钮时,e.target会指向按钮,导致按钮也被加上删除线,这不是预期行为。

解决方案

方案1:仅给新创建的li绑定事件(简单修正)

不用每次提交都遍历所有li,只给刚创建的newTodo绑定一次点击事件,同时判断点击的目标是不是li的文本部分,避免按钮被误操作:

const ul = document.querySelector('ul')
const submitTodo = document.querySelector('input[type="submit"]')
const addTodo = document.querySelector('input[type="text"]')

submitTodo.addEventListener('click', function(e) {
  e.preventDefault()
  // 空内容不添加
  if (!addTodo.value.trim()) return
  
  let newTodo = document.createElement('li')
  newTodo.innerText = addTodo.value

  let newTodoRemove = document.createElement('button')
  newTodoRemove.innerText = 'Remove To-do'

  ul.appendChild(newTodo)
  newTodo.appendChild(newTodoRemove)

  // 只给新创建的li绑定一次事件
  newTodo.addEventListener('click', function(e) {
    // 点击的是按钮的话,不执行删除线逻辑
    if (e.target.tagName === 'BUTTON') return
    // 切换删除线样式,用getComputedStyle判断更准确(避免内联样式未设置的情况)
    const currentStyle = window.getComputedStyle(this)
    if (currentStyle.textDecoration.includes('line-through')) {
      this.style.textDecoration = 'none'
    } else {
      this.style.textDecoration = 'line-through'
    }
  })

  // 清空输入框
  addTodo.value = ''
})

方案2:事件委托(更优方案)

把点击事件绑定在父元素<ul>上,利用事件冒泡机制,不管后续添加多少li,都能响应点击。这种方式不用每次添加元素都绑定事件,性能更优:

const ul = document.querySelector('ul')
const submitTodo = document.querySelector('input[type="submit"]')
const addTodo = document.querySelector('input[type="text"]')

// 事件委托:绑定在ul上,处理所有li的点击
ul.addEventListener('click', function(e) {
  // 只处理li元素的点击
  const li = e.target.closest('li')
  if (!li) return
  // 点击的是按钮的话,执行删除逻辑
  if (e.target.tagName === 'BUTTON') {
    li.remove()
    return
  }
  // 切换删除线样式
  const currentStyle = window.getComputedStyle(li)
  li.style.textDecoration = currentStyle.textDecoration.includes('line-through') ? 'none' : 'line-through'
})

submitTodo.addEventListener('click', function(e) {
  e.preventDefault()
  if (!addTodo.value.trim()) return
  
  let newTodo = document.createElement('li')
  newTodo.innerText = addTodo.value

  let newTodoRemove = document.createElement('button')
  newTodoRemove.innerText = 'Remove To-do'

  ul.appendChild(newTodo)
  newTodo.appendChild(newTodoRemove)

  addTodo.value = ''
})

额外优化点

  • 添加了空内容判断,避免创建空的li
  • 用window.getComputedStyle判断样式,比直接读e.target.style更准确(因为style只能读取内联样式,浏览器默认样式或外部样式表的样式读不到)
  • 方案2里给删除按钮添加了删除li的功能,更符合待办列表的需求

内容的提问来源于stack exchange,提问作者Vincent Schultz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:32:06