类中带事件与参数的removeEventListener失效问题求助
问题描述
我正在构建一个处理Symfony表单AJAX请求与响应的类,通过MutationObserver监听DOM变化,为新增表单重新绑定事件监听器。为避免同一表单绑定多个重复事件,需要移除除最后一个外的所有监听器,但removeEventListener始终无法生效。
已知addEventListener和removeEventListener必须使用同一个函数实例,问题应该出在sendAsyncForm返回的匿名函数上——每次调用sendAsyncForm都会生成新的函数,导致移除时找不到匹配的监听器。但我需要同时传递事件对象和自定义参数,尝试多种方式都没解决,特此求助。
HTML代码
<table class="table" id="admin__table"> <thead> <tr> <th>Id</th> <th>Nombre</th> <th>Email</th> <th>Roles</th> <th>CreationDate</th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> {% for admin in admins %} <tr {% if not admin.isActive %}style="background-color: #ffaaaa;"{% endif %}> <td>{{ admin.id }}</td> <td> {% set showPath = path('app_admin_show', {'id': admin.id}) %} <div class="text-primary" data-mdb-toggle="modal" data-mdb-target="#crudModal" type="button" onclick="asyncHTML('{{ showPath }}', 'crud-modal__body')"> {{ admin.name }} {{ admin.firstname }} </div> </td> <td>{{ admin.email }}</td> <td>{{ admin.roles ? admin.roles|json_encode : '' }}</td> <td>{{ admin.creationDate ? admin.creationDate|date('Y-m-d H:i:s') : '' }}</td> <td> <div data-mdb-toggle="modal" data-mdb-target="#crudModal"> {% include "components/admin/triggers/admin_edit-button.html.twig" with {'path': path('app_admin_edit', {'id': admin.id}), 'HTMLPlaceDOMelementId': 'crud-modal__body'} %} </div> </td> <td>{% include "admin/_block_form.html.twig" %}</td> <td>{% include "admin/_delete_form.html.twig" %}</td> </tr> {% else %} <tr> <td colspan="10">no records found</td> </tr> {% endfor %} </tbody> </table> <script> blockFormObserver(); function blockFormObserver() { let observer = new MutationObserver(function(mutations, observer) { let forms = document.querySelectorAll("form[async][name='admin__block']"); let asyncFormArray = []; for (let i = 0; i < forms.length; i++) { asyncFormArray[i] = new AsyncForm(forms[i]); asyncFormArray[i].setResponse(function() {response => console.log("response")}); asyncFormArray[i].setOnsuccess(() => asyncHTML("{{ path('app_admin_index') }}", "admin__index")); asyncFormArray[i].prepare(); if (i < forms.length-1) asyncFormArray[i].remove(); } }); observer.observe(document, { subtree: true, attributes: true, }); } </script>
JavaScript代码
class AsyncForm { constructor(form) { if (form == null) throw new AsyncFormException('Form passed to constructor is null', this.form); if (form.getAttribute("action") == null) throw new AsyncFormException('Action attribute wasn`t found', this.form); this.form = form; this.action = this.form.getAttribute("action"); this.previous = false; this.response = false; this.onsuccess = false; } prepare() { this.form.addEventListener("submit", this.sendAsyncForm(this.response, this.onsuccess)); } remove() { this.form.removeEventListener("submit", this.sendAsyncForm(this.response, this.onsuccess)); } sendAsyncForm(response, onsuccess) { return (e) => { e.preventDefault(); console.log(response); var formData = new FormData(this.form); $.ajax({ url : this.action, type : 'POST', data : formData, cache: false, processData: false, contentType: false, dataType : 'html', success : response => { if (this.onsuccess) this.onsuccess(); }, error : (xhr, status) => { if (xhr.status = 422) this.response(xhr.responseText); else response("<div class='form-error'>Ocurrió un problema al enviar el formulario, disculpe las molestias.</div>"); }, }); } } setAction(action) { this.action = action; } setPrevious(previous) { this.previous = previous; } setResponse(response) { this.response = response; } setOnsuccess(onsuccess) { this.onsuccess = onsuccess; } } function AsyncFormException(message, form) { this.message = message; this.form = form; this.name = "AsyncFormException"; }
解决方案
核心问题是每次调用sendAsyncForm都会生成全新的函数实例,导致addEventListener和removeEventListener引用的不是同一个函数。解决思路是把生成的回调函数存储在AsyncForm实例中,确保添加和移除时用同一个引用。
修改后的AsyncForm类
class AsyncForm { constructor(form) { if (form == null) throw new AsyncFormException('Form passed to constructor is null', this.form); if (form.getAttribute("action") == null) throw new AsyncFormException('Action attribute wasn`t found', this.form); this.form = form; this.action = this.form.getAttribute("action"); this.previous = false; this.response = false; this.onsuccess = false; this.submitHandler = null; // 存储回调函数引用 } prepare() { // 先移除已存在的监听器(防止重复绑定) if (this.submitHandler) { this.form.removeEventListener("submit", this.submitHandler); } // 生成并存储回调函数 this.submitHandler = (e) => { e.preventDefault(); console.log(this.response); var formData = new FormData(this.form); $.ajax({ url : this.action, type : 'POST', data : formData, cache: false, processData: false, contentType: false, dataType : 'html', success : () => { if (this.onsuccess) this.onsuccess(); }, error : (xhr, status) => { if (xhr.status === 422) { // 修复赋值为比较的错误 this.response(xhr.responseText); } else { this.response("<div class='form-error'>Ocurrió un problema al enviar el formulario, disculpe las molestias.</div>"); } }, }); }; this.form.addEventListener("submit", this.submitHandler); } remove() { if (this.submitHandler) { this.form.removeEventListener("submit", this.submitHandler); this.submitHandler = null; // 清空引用 } } // 其他方法保持不变 setAction(action) { this.action = action; } setPrevious(previous) { this.previous = previous; } setResponse(response) { this.response = response; } setOnsuccess(onsuccess) { this.onsuccess = onsuccess; } } function AsyncFormException(message, form) { this.message = message; this.form = form; this.name = "AsyncFormException"; }
MutationObserver逻辑优化
原代码中每次DOM变化都会遍历所有表单并尝试移除前N-1个的监听器,可简化为直接为每个表单创建实例时先解绑再绑定,避免重复绑定:
function blockFormObserver() { let observer = new MutationObserver(function(mutations, observer) { let forms = document.querySelectorAll("form[async][name='admin__block']"); forms.forEach(form => { // 检查表单是否已绑定过AsyncForm实例,避免重复创建 if (!form._asyncFormInstance) { form._asyncFormInstance = new AsyncForm(form); form._asyncFormInstance.setResponse(response => console.log("response")); form._asyncFormInstance.setOnsuccess(() => asyncHTML("{{ path('app_admin_index') }}", "admin__index")); } // 先移除再重新绑定,确保只有一个监听器生效 form._asyncFormInstance.remove(); form._asyncFormInstance.prepare(); }); }); observer.observe(document, { subtree: true, attributes: true, childList: true // 新增监听DOM节点增删,更准确捕获表单新增 }); }
关键修改说明
- 存储回调引用:通过
submitHandler属性保存回调函数,确保add和remove使用同一个函数实例。 - 移除冗余方法:删除
sendAsyncForm,直接在prepare中定义回调,避免每次调用生成新函数。 - 修复AJAX错误:把
xhr.status = 422的赋值操作改为===比较。 - 避免重复实例化:给表单标记
_asyncFormInstance属性,减少不必要的对象创建。 - 优化Observer配置:添加
childList: true,更精准监听表单的新增操作。
内容的提问来源于stack exchange,提问作者Diego García
相关产品推荐
相关产品推荐

