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'; });
问题原因分析
核心问题是事件监听重复绑定且无法正确移除:
- 每次进入
editTheTableColums函数时,都会给addColumn按钮绑定一个新的匿名点击回调函数。 - 返回表创建界面时,执行的
addColumn.removeEventListener('click', addColumnFunc)无效——因为移除监听需要和绑定的是同一个函数引用,但这里绑定的是匿名函数,和addColumnFunc不是同一个引用,旧的监听完全没被移除。 - 多次进出列创建界面后,
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
相关产品推荐
相关产品推荐

