无需setTimeout解决focusout与keydown事件冲突问题及原因
事件冲突原因及解决方案
冲突产生的原因
当按下未按住Shift的Enter键时,事件触发逻辑和DOM操作会导致双重执行:
- 首先触发textarea的
keydown事件,执行回调里的任务更新和页面渲染逻辑。 - 渲染逻辑会移除当前页面的
.content元素(包含目标textarea),而DOM元素被从文档树中移除时,浏览器会自动触发该元素的focusout事件,导致同一个回调函数被再次调用。 - 两次同步执行的更新和渲染逻辑会导致重复操作数据、重复修改DOM,进而引发异常。
不用setTimeout的解决方法
方法1:添加标志位阻止重复执行
通过一个布尔变量标记是否正在处理逻辑,避免同一操作触发的两次事件重复执行:
// Page content class const descriptionInput = document.createElement('textarea'); let isUpdating = false; // 新增标志位 const handleDescription = function(e){ if (isUpdating) return; // 正在处理则直接返回 const shouldProcess = (e.key === "Enter" && !e.shiftKey) || (e.type === "focusout"); if (shouldProcess) { isUpdating = true; // 可选:阻止Enter默认的换行行为 if (e.key === "Enter") e.preventDefault(); task.description = this.value; storage.updateTask(task.id, task); (new Render).refreshPage(); isUpdating = false; } } descriptionInput.addEventListener('keydown', handleDescription) descriptionInput.addEventListener('focusout', handleDescription)
方法2:拆分事件回调并动态解绑
将keydown和focusout的逻辑拆分,在keydown触发后解绑focusout事件,避免元素被移除时触发重复逻辑:
// Page content class const descriptionInput = document.createElement('textarea'); // 提取公共逻辑 const updateTaskAndRefresh = function() { task.description = this.value; storage.updateTask(task.id, task); (new Render).refreshPage(); } // 单独处理keydown事件 const handleKeydown = function(e){ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); // 阻止换行 updateTaskAndRefresh.call(this); // 解绑focusout事件,避免元素被移除时触发 this.removeEventListener('focusout', handleFocusout); } } // 单独处理focusout事件 const handleFocusout = function(){ updateTaskAndRefresh.call(this); } descriptionInput.addEventListener('keydown', handleKeydown) descriptionInput.addEventListener('focusout', handleFocusout)
内容的提问来源于stack exchange,提问作者i5x
相关产品推荐
相关产品推荐

