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

待办列表Edit按钮异常 表单submit事件监听器重复触发问题

问题描述
  • 待办列表(To-Do List)项目现有功能逻辑:
    • 支持创建多个项目,用户可在每个项目内通过弹窗表单添加任务,每个任务配有Delete、Edit两个操作按钮
    • 点击任务的Edit按钮时,弹出预填充当前任务全部信息的编辑表单供用户修改
  • 现存异常:首次编辑任意任务提交后可正常更新信息,但后续编辑其他任务提交时,会连带修改之前编辑过的任务
  • 问题初步定位:编辑弹窗表单的submit事件监听器重复绑定。负责绑定监听器的setEditTaskEvents函数接收taskId参数标识当前待编辑任务,该函数在Edit按钮的click事件回调中被调用。每次点击Edit按钮时,都会给id为edit-task-form的表单新增绑定一个submit事件监听器,已绑定的旧监听器不会被移除,导致表单提交时会依次执行所有历史绑定的监听器逻辑,同步修改所有曾绑定过监听器的任务。
  • 实现目标:表单提交事件监听器每次仅针对单个待编辑任务生效。
异常相关原始代码
let edit = document.createElement('button');
edit.textContent = 'Edit';
edit.classList.add('edit');
edit.id = `edit-${item.id}`;
edit.addEventListener('click', (e) => {
   let taskId = document.getElementById(e.target.id).parentNode.id;
   setEditTaskFields(taskId);
   setEditTaskEvents(taskId);
})

function setEditTaskEvents(taskId) {
        const project = selectDisplayedProject()
        const byId = project.items.map(e => e.id);

        const task = selectDisplayedProject().items.filter(task => task.id == taskId)[0];
        document.getElementById('edit-task-form').addEventListener('submit', ()=> {
            let title = document.querySelector('#edit-task-form #title').value;
            let description = document.querySelector('#edit-task-form .description').value;
            let dueDate = document.querySelector('#edit-task-form #due-date').value;
            document.querySelectorAll('#edit-task-form .priority-container input').forEach(radio => {
                if (radio.checked) project.items[byId.indexOf(taskId)] = Item(title, description, dueDate, radio.value, selectDisplayedProject());
            });
            displayProject(selectDisplayedProject());
            document.querySelector('.edit-task-popup').style.display = 'none';
        })
}
解决方案

DOM事件监听器不会被新绑定的同类型监听器自动覆盖,重复调用addEventListener会将所有回调按绑定顺序加入执行队列,事件触发时全部执行。有两种可落地的修复方案:

  • 方案1(最小改动):将submit事件的回调定义为具名函数,每次绑定新监听器前,先调用removeEventListener清除上一次绑定的同事件回调。但这种方式依赖手动执行移除逻辑,后续代码迭代时容易遗漏导致问题复现。
  • 方案2(推荐):全局仅绑定一次submit监听器,用独立变量存储当前待编辑的任务ID。点击Edit按钮时只更新这个变量的值,表单提交时直接读取最新ID执行更新逻辑,从根源上避免重复绑定问题,代码可维护性更高。
修正后实现代码(采用方案2)
// 全局新增变量,存储当前正在编辑的任务ID
let currentEditingTaskId = null;

// Edit按钮点击逻辑
let edit = document.createElement('button');
edit.textContent = 'Edit';
edit.classList.add('edit');
edit.id = `edit-${item.id}`;
edit.addEventListener('click', (e) => {
   // 直接从触发事件的按钮父节点取任务ID,无需重复查询DOM
   let taskId = e.target.parentNode.id;
   setEditTaskFields(taskId);
   // 仅更新当前编辑的任务ID,不再重复绑定表单事件
   currentEditingTaskId = taskId;
   document.querySelector('.edit-task-popup').style.display = 'block';
})

// 页面初始化阶段执行一次即可,不要放在Edit按钮点击回调中
document.getElementById('edit-task-form').addEventListener('submit', (e)=> {
    // 阻止表单默认提交的页面刷新行为
    e.preventDefault();
    if (!currentEditingTaskId) return;
    const project = selectDisplayedProject();
    // 直接通过findIndex定位任务位置,比map+indexOf的写法更高效直观
    const taskIndex = project.items.findIndex(task => task.id == currentEditingTaskId);
    if (taskIndex === -1) return;

    const title = document.querySelector('#edit-task-form #title').value;
    const description = document.querySelector('#edit-task-form .description').value;
    const dueDate = document.querySelector('#edit-task-form #due-date').value;
    document.querySelectorAll('#edit-task-form .priority-container input').forEach(radio => {
        if (radio.checked) {
            project.items[taskIndex] = Item(title, description, dueDate, radio.value, project);
        }
    });
    displayProject(project);
    document.querySelector('.edit-task-popup').style.display = 'none';
    // 提交后清空当前编辑任务ID,避免误操作
    currentEditingTaskId = null;
})

// 原有setEditTaskEvents函数可直接删除,无需再调用

额外优化点:原始代码中多次重复调用selectDisplayedProject(),可将返回值存入变量复用,减少不必要的函数执行开销。

内容的提问来源于stack exchange,提问作者jacob-ferrell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:03:27