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

ElectronJS中AddEventListener点击事件多次触发问题排查

问题描述

基于ElectronJS开发MySQL数据库生成桌面应用,通过自研图形界面交互。在为已有数据表添加列时出现异常:每次点击添加列按钮,实际新增的列数都会超出预期。规律明确:每从列创建界面返回表创建界面一次,再次进入任意表的列创建界面后,点击添加列按钮时新增列数就会增加1。比如3次进出列创建界面后,第4次点击会新增4列。

控制台日志(创建3个表,依次尝试为每个表添加1列):

debug 1
temp.js:132 debug 2
temp.js:179 debug 3
temp.js:226 back
temp.js:91 debug 1
temp.js:132 debug 2
2temp.js:179 debug 3
temp.js:226 back
temp.js:91 debug 1
temp.js:132 debug 2
3temp.js:179 debug 3

注:已尝试添加event.stopPropagation(),未解决问题。


相关代码实现

创建新表的代码

addTable.addEventListener('click', () => {

    // 创建表容器
    const tableContainer = document.createElement('div');
    tableContainer.setAttribute('class', 'tableContainer')
    tableContainer.setAttribute('id', `tableContainer_${numberOfTables}`)
    newTableContainer.appendChild(tableContainer);

    /* 表名称输入框 */
    const tableName = document.createElement('input');
    tableName.setAttribute('id', `tableName_${numberOfTables}`);
    tableName.setAttribute('class', 'input');
    tableName.setAttribute('type', 'text');
    tableName.setAttribute('placeholder', 'Table name: ');
    tableContainer.appendChild(tableName);

    /* 添加列按钮 */
    const addColumns = document.createElement('input');
    addColumns.setAttribute('id', `addColumnsButton_${numberOfTables}`);
    addColumns.setAttribute('class', 'add-columns-button table-creator-button');
    addColumns.setAttribute('type', 'button');
    addColumns.setAttribute('data-table-number', numberOfTables)
    addColumns.setAttribute('value', 'Add columns');
    tableContainer.appendChild(addColumns);

    // 绑定添加列按钮点击事件
    addColumns.addEventListener('click', () => {
        let buttonNumber = addColumns.getAttribute('data-table-number');
        let tableToEdit = document.getElementById(`tableName_${buttonNumber}`);

        if (tableToEdit) {
            console.log("debug 1")
            editTheTableColums(tableToEdit, buttonNumber);
        }
    });


    /* 删除表按钮 */
    const deleteTheTable = document.createElement('input');
    deleteTheTable.setAttribute('id', `deleteTheTableButton_${numberOfTables}`);
    deleteTheTable.setAttribute('class', 'delete-the-table-button table-creator-button');
    deleteTheTable.setAttribute('type', 'button');
    deleteTheTable.setAttribute('value', 'Delete this table');
    deleteTheTable.setAttribute('data-table-number', numberOfTables)
    tableContainer.appendChild(deleteTheTable);

    // 绑定删除表按钮点击事件
    deleteTheTable.addEventListener('click', () => {
        let buttonNumber = deleteTheTable.getAttribute('data-table-number');
        let tableToDelete = document.getElementById(`tableContainer_${buttonNumber}`);

        if (tableToDelete) {
            tableToDelete.parentNode.removeChild(tableToDelete);
        }
    });


    numberOfTables++;
});

处理表列添加的函数

/* 处理为表添加列的逻辑 */
function editTheTableColums(tableToEdit, buttonNumber) {
    console.log("debug 2")
    let numberOfColumns = 0;

    /* 检查表名称是否为空 */
    if (tableToEdit.value.trim() != "") {

        mainH1.innerHTML = `Adding columns to the table <i>${tableToEdit.value}</i>`;

        /* 切换页面显示 */
        newTableContainer.style.display = "none";
        editColumnsContainer.style.display = "inline";

        /* 切换按钮可见性 */
        addTable.style.display = "none";
        backToMainMenu.style.display = "none";
        backToTheTableCreator.style.display = "inline";
        addColumn.style.display = "inline";
        confirm.style.display = 'inline';

        // 创建与当前表关联的列容器
        const tableColumns = document.createElement('div');
        tableColumns.setAttribute('class', 'tableColumns')
        tableColumns.setAttribute('id', `tableColumns_${buttonNumber}`)
        editColumnsContainer.appendChild(tableColumns);

        let currentTable = document.getElementById(`tableColumns_${buttonNumber}`)

        // 隐藏其他表的列容器
        for (let i = 0; i < editColumnsContainer.children.length; i++) {
            editColumnsContainer.children[i].style.display = "none";
        }

        currentTable.style.display = "inline";


        /* 绑定添加列按钮事件 */
        addColumn.addEventListener('click', () => {
            addColumnFunc(buttonNumber, numberOfColumns, currentTable);
        }); // 问题出在这里
          
    }
}

列处理的其他函数

const addColumnFunc = function(buttonNumber, numberOfColumns, currentTable) {
    
    console.log("debug 3")
    const currentButtonNumber = buttonNumber;

    // 创建列容器
    const columnContainer = document.createElement('div');
    columnContainer.setAttribute('class', 'columnContainer')
    columnContainer.setAttribute('id', `columnContainer_${numberOfColumns}_${currentButtonNumber}`)
    currentTable.appendChild(columnContainer);

    let currentColumnContainer = document.getElementById(`columnContainer_${numberOfColumns}_${currentButtonNumber}`);

    /* 列名称输入框 */
    const columnName = document.createElement('input');
    columnName.setAttribute('id', `columnName_${numberOfColumns}_${currentButtonNumber}`);
    columnName.setAttribute('class', 'input column-name');
    columnName.setAttribute('type', 'text');
    columnName.setAttribute('placeholder', 'Column name: ');
    currentColumnContainer.appendChild(columnName);

    /* 删除列按钮 */
    const deleteTheColumn = document.createElement('input');
    deleteTheColumn.setAttribute('id', `deleteTheColumnButton_${numberOfColumns}_${currentButtonNumber}`);
    deleteTheColumn.setAttribute('class', 'delete-the-column-button column-creator-button');
    deleteTheColumn.setAttribute('type', 'button');
    deleteTheColumn.setAttribute('value', 'Delete');
    deleteTheColumn.setAttribute('data-column-number', numberOfColumns)
    currentColumnContainer.appendChild(deleteTheColumn);

    // 绑定删除列按钮事件
    deleteTheColumn.addEventListener('click', () => {
        let buttonNumber = deleteTheColumn.getAttribute('data-column-number');
        let columnToDelete = document.getElementById(`columnContainer_${numberOfColumns}_${buttonNumber}`);

        if (columnToDelete) {
            columnToDelete.parentNode.removeChild(columnToDelete);
        }
    });


    numberOfColumns++;
};


/* 从列创建界面返回表创建界面 */
backToTheTableCreator.addEventListener('click', () => {
    console.log("back")

    mainH1.innerHTML = `Editing ${databaseName}`;

    addColumn.removeEventListener('click', addColumnFunc);

    /* 切换页面显示 */
    newTableContainer.style.display = "inline";
    editColumnsContainer.style.display = "none";

    /* 切换按钮可见性 */
    addTable.style.display = "inline";
    backToMainMenu.style.display = "inline";
    backToTheTableCreator.style.display = "none";
    document.getElementById("add-column").style.display = "none";
    confirm.style.display = 'none';

});

问题原因分析

核心问题是事件监听重复绑定且无法正确移除:

  1. 每次进入editTheTableColums函数时,都会给addColumn按钮绑定一个新的匿名点击回调函数。
  2. 返回表创建界面时,执行的addColumn.removeEventListener('click', addColumnFunc)无效——因为移除监听需要和绑定的是同一个函数引用,但这里绑定的是匿名函数,和addColumnFunc不是同一个引用,旧的监听完全没被移除。
  3. 多次进出列创建界面后,addColumn按钮上会累积多个点击监听,每次点击都会触发所有累积的回调,导致新增列数等于累积的监听数量。

解决方案

方案1:使用可追踪的具名回调函数

通过保存当前绑定的回调引用,确保绑定和移除的是同一个函数:

修改editTheTableColums函数

// 全局变量保存当前的添加列回调,用于后续移除
let currentAddColumnCallback;

function editTheTableColums(tableToEdit, buttonNumber) {
    console.log("debug 2")
    let numberOfColumns = 0;

    if (tableToEdit.value.trim() != "") {
        mainH1.innerHTML = `Adding columns to the table <i>${tableToEdit.value}</i>`;

        newTableContainer.style.display = "none";
        editColumnsContainer.style.display = "inline";

        addTable.style.display = "none";
        backToMainMenu.style.display = "none";
        backToTheTableCreator.style.display = "inline";
        addColumn.style.display = "inline";
        confirm.style.display = 'inline';

        const tableColumns = document.createElement('div');
        tableColumns.setAttribute('class', 'tableColumns')
        tableColumns.setAttribute('id', `tableColumns_${buttonNumber}`)
        editColumnsContainer.appendChild(tableColumns);

        let currentTable = document.getElementById(`tableColumns_${buttonNumber}`)

        for (let i = 0; i < editColumnsContainer.children.length; i++) {
            editColumnsContainer.children[i].style.display = "none";
        }

        currentTable.style.display = "inline";

        // 先移除之前绑定的回调(如果存在)
        if (currentAddColumnCallback) {
            addColumn.removeEventListener('click', currentAddColumnCallback);
        }

        // 定义具名回调函数,捕获当前的变量
        currentAddColumnCallback = () => {
            addColumnFunc(buttonNumber, numberOfColumns, currentTable);
        };

        // 绑定新的回调
        addColumn.addEventListener('click', currentAddColumnCallback);
    }
}

修改返回按钮的处理函数

backToTheTableCreator.addEventListener('click', () => {
    console.log("back")

    mainH1.innerHTML = `Editing ${databaseName}`;

    // 移除当前绑定的回调,并清空变量
    if (currentAddColumnCallback) {
        addColumn.removeEventListener('click', currentAddColumnCallback);
        currentAddColumnCallback = null;
    }

    newTableContainer.style.display = "inline";
    editColumnsContainer.style.display = "none";

    addTable.style.display = "inline";
    backToMainMenu.style.display = "inline";
    backToTheTableCreator.style.display = "none";
    document.getElementById("add-column").style.display = "none";
    confirm.style.display = 'none';
});

方案2:优化列编号逻辑(可选)

避免闭包变量带来的潜在问题,直接用当前表的子元素数量作为列编号:

const addColumnFunc = function(buttonNumber, currentTable) {
    console.log("debug 3")
    const currentButtonNumber = buttonNumber;
    // 直接用当前表的子元素数量作为列编号,无需依赖外部变量
    let numberOfColumns = currentTable.children.length;

    const columnContainer = document.createElement('div');
    columnContainer.setAttribute('class', 'columnContainer')
    columnContainer.setAttribute('id', `columnContainer_${numberOfColumns}_${currentButtonNumber}`)
    currentTable.appendChild(columnContainer);

    let currentColumnContainer = document.getElementById(`columnContainer_${numberOfColumns}_${currentButtonNumber}`);

    const columnName = document.createElement('input');
    columnName.setAttribute('id', `columnName_${numberOfColumns}_${currentButtonNumber}`);
    columnName.setAttribute('class', 'input column-name');
    columnName.setAttribute('type', 'text');
    columnName.setAttribute('placeholder', 'Column name: ');
    currentColumnContainer.appendChild(columnName);

    const deleteTheColumn = document.createElement('input');
    deleteTheColumn.setAttribute('id', `deleteTheColumnButton_${numberOfColumns}_${currentButtonNumber}`);
    deleteTheColumn.setAttribute('class', 'delete-the-column-button column-creator-button');
    deleteTheColumn.setAttribute('type', 'button');
    deleteTheColumn.setAttribute('value', 'Delete');
    deleteTheColumn.setAttribute('data-column-number', numberOfColumns)
    currentColumnContainer.appendChild(deleteTheColumn);

    deleteTheColumn.addEventListener('click', () => {
        // 直接删除当前列容器,无需查找ID
        currentColumnContainer.parentNode.removeChild(currentColumnContainer);
    });
};

修改后,调用addColumnFunc时不需要传递numberOfColumns,在editTheTableColums的回调里改为:

currentAddColumnCallback = () => {
    addColumnFunc(buttonNumber, currentTable);
};

内容的提问来源于stack exchange,提问作者Alex-GR

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:17:35