无法移除JavaScript元素事件监听器问题求助
问题原因分析
你遇到的核心问题是:draggableProgressBar是一个高阶函数,每次调用draggableProgressBar(el)都会返回一个全新的函数实例。这就导致添加监听时用的是一个函数,移除时调用同样方法又生成了另一个完全不同的函数引用——removeEventListener只能移除与添加时完全相同的函数引用,所以自然失效。
解决方案:存储函数引用
要解决这个问题,关键是把每个元素对应的mousemove处理函数保存下来,确保移除时用的是同一个引用。下面是两种可行的实现方式:
方式一:利用元素自定义属性存储
修改代码如下:
const videoTimeline = document.querySelectorAll(".video-timeline"); // 调整变量名避免参数混淆:el是timeline元素,e是mousemove事件对象 const draggableProgressBar = el => function(e) { const container = el.closest(".container"); const video = container.querySelector("video"); const timelineWidth = el.clientWidth; container.querySelector(".progress-bar").style.width = `${e.offsetX}px`; video.currentTime = (e.offsetX / timelineWidth) * video.duration; container.querySelector(".current-time").innerText = formatTime(video.currentTime); }; videoTimeline.forEach(el => { el.addEventListener("mousedown", () => { // 生成处理函数并存储到元素的自定义属性中 const handler = draggableProgressBar(el); el.dataset.dragHandler = handler; el.addEventListener("mousemove", handler); }); }); document.addEventListener("mouseup", () => { videoTimeline.forEach(el => { // 取出存储的函数引用 const handler = el.dataset.dragHandler; if (handler) { el.removeEventListener("mousemove", handler); // 移除后清空存储,避免内存泄漏 delete el.dataset.dragHandler; } }); });
方式二:用Map存储元素与函数的对应关系
如果不想修改元素属性,可以用Map来管理引用:
const videoTimeline = document.querySelectorAll(".video-timeline"); const handlerMap = new Map(); // 存储元素对应的处理函数 const draggableProgressBar = el => function(e) { const container = el.closest(".container"); const video = container.querySelector("video"); const timelineWidth = el.clientWidth; container.querySelector(".progress-bar").style.width = `${e.offsetX}px`; video.currentTime = (e.offsetX / timelineWidth) * video.duration; container.querySelector(".current-time").innerText = formatTime(video.currentTime); }; videoTimeline.forEach(el => { el.addEventListener("mousedown", () => { const handler = draggableProgressBar(el); handlerMap.set(el, handler); el.addEventListener("mousemove", handler); }); }); document.addEventListener("mouseup", () => { videoTimeline.forEach(el => { const handler = handlerMap.get(el); if (handler) { el.removeEventListener("mousemove", handler); handlerMap.delete(el); } }); });
额外优化建议
- 多次调用
querySelector会重复DOM查询,建议提前获取容器内的元素(比如在mousedown时一次性获取),提升性能。 - 变量名尽量清晰,原代码中
draggableProgressBar的参数e其实是DOM元素,容易和事件对象混淆,上面的代码已经调整为el,避免歧义。
内容的提问来源于stack exchange,提问作者Marcus Wright
相关产品推荐
相关产品推荐

