如何移除Excel工作簿/工作表/表格注册的所有事件处理程序
我正在使用ScriptLab调试事件处理程序,每次运行脚本时都会注册新的事件处理程序。我已知晓移除单个事件处理程序的方法,但每次代码执行刷新后,上一次执行注册的事件处理程序就无法删除,导致事件处理程序不断堆叠,会随新代码一同触发执行。
我希望了解是否可以移除工作表、表格、工作簿等对象上绑定的所有事件处理程序,是否能像原生JavaScript那样访问已注册的事件处理程序集合并执行删除操作。
上下文示例代码
$("#run").click(() => tryCatch(run)); $("#remove").click(() => tryCatch(remove)); let eventResult; async function run() { await Excel.run(async (context) => { const worksheet = context.workbook.worksheets.getItem("Sample"); eventResult = worksheet.onSelectionChanged.add(handleSelectionChange); await context.sync(); console.log("Event handler successfully registered for onSelectionChanged event in the worksheet."); }); } async function handleSelectionChange(event) { await Excel.run(async (context) => { await context.sync(); console.log("Address of current selection: " + event.address); }); } async function remove() { await Excel.run(eventResult.context, async (context) => { eventResult.remove(); await context.sync(); eventResult = null; console.log("Event handler successfully removed."); }); } /** Default helper for invoking an action and handling errors. */ async function tryCatch(callback) { try { await callback(); } catch (error) { // Note: In a production add-in, you'd want to notify the user through your add-in's UI. console.error(error); } }
如示例代码所示,若将事件处理程序注册返回的对象存储在变量(eventResult)中,可很方便地移除对应处理程序。但只要我在ScriptLab代码窗格中做任何修改,所有变量都会被重置,而之前注册的事件仍在工作表中保持激活状态。
我需要一种即使代码脱离上下文、变量丢失状态后,仍可移除所有已注册事件的实现方法。
Office JS 事件模型本身没有提供枚举对象上所有已绑定事件处理程序的API,做不到像原生JavaScript那样直接读取已注册的handler集合批量删除,针对ScriptLab调试场景,可按以下方式处理:
调试场景快速清理
ScriptLab中修改代码触发刷新时,原来的代码上下文会被销毁,但之前注册的事件会绑定在当前外接程序运行实例上残留,不需要写代码就能快速清理:
- 点击ScriptLab侧边栏的首页按钮,再重新打开当前调试的代码片段,原有外接程序实例会被销毁,所有残留事件会自动被清除
- 如果上述操作无效,直接关闭当前工作簿后重新打开,所有历史事件绑定会全部清空
代码层面可控清理方案
如果需要不重载实例/工作簿就能清理所有历史事件,核心思路是不要把事件注册返回的对象存在会被代码刷新重置的普通变量中,把引用持久化到不会被重置的存储位置即可:
调试阶段直接把事件引用挂载到window全局对象上,每次注册新事件前先清理历史事件,改造后的示例代码如下:
// 初始化全局事件存储,代码刷新时不会被重置 window._excelEventHandlers = window._excelEventHandlers || []; $("#run").click(() => tryCatch(run)); $("#removeAll").click(() => tryCatch(removeAllHandlers)); async function run() { await Excel.run(async (context) => { // 注册新事件前先清理所有历史事件,避免堆叠 await removeAllHandlers(); const worksheet = context.workbook.worksheets.getItem("Sample"); const newEvent = worksheet.onSelectionChanged.add(handleSelectionChange); window._excelEventHandlers.push(newEvent); await context.sync(); console.log("事件注册成功"); }); } async function removeAllHandlers() { if (!window._excelEventHandlers.length) return; await Excel.run(async (context) => { for (const handler of window._excelEventHandlers) { handler.remove(); } window._excelEventHandlers = []; await context.sync(); console.log("所有历史事件已移除"); }); } // 其余handleSelectionChange、tryCatch方法和原示例保持一致即可
生产环境注意点
正式发布的外接程序不会出现ScriptLab这种代码热刷新导致变量丢失的场景,只要在外接程序初始化逻辑里提前清理上一次运行注册的事件,再绑定新的事件逻辑,就不会出现事件堆叠问题。
内容的提问来源于stack exchange,提问作者Felipe Costa Gualberto

