纯JavaScript中的事件处理程序命名空间

46

我熟悉jQuery事件处理程序中的命名空间。我可以在特定命名空间中添加事件处理程序:

$('#id').on('click.namespace', _handlerFunction);

然后我可以删除该命名空间中的所有事件处理程序:

$('#id').off('.namespace');

这里的优点是我只能删除此命名空间中的事件,而不是任何应该被保留的用户添加/附加事件。

有没有人有关于如何不使用jQuery,但实现类似结果的提示?


http://danml.com/js/events.js 有类似的东西:你可以使用正则表达式进行选择。还可以在 bower 和 microjs 中搜索“event”以获取更多类似的内容... - dandavis
4个回答

43

对于仍在寻找的人,我最终创建了一个帮助器单例,它会为我跟踪函数引用。

class EventHandlerClass {
  constructor() {
    this.functionMap = {};
  }

  addEventListener(event, func) {
    this.functionMap[event] = func;
    document.addEventListener(event.split('.')[0], this.functionMap[event]);
  }

  removeEventListener(event) {
    document.removeEventListener(event.split('.')[0], this.functionMap[event]);
    delete this.functionMap[event];
  }
}

export const EventHandler = new EventHandlerClass();

然后只需导入EventHandler并像这样使用:

EventHandler.addEventListener('keydown.doop', () => console.log("Doop"));
EventHandler.addEventListener('keydown.wap', () => console.log("Wap"));
EventHandler.removeEventListener('keydown.doop');
// keydown.wap is still bound

1
简洁之美 +1 - Scott L
1
非常好!保存回调函数的引用是个好主意。 - scipper
1
是的,我喜欢这个。它是所有中最干净的。 - Sampgun
@MaxCore 是的,https://developer.mozilla.org/zh-CN/docs/Web/API/Event/Event - ADJenks
1
如果您在不删除先前事件的情况下添加具有相同签名的事件,则会丢失对先前事件的引用,该项上将存在两个事件,但只有一个绑定引用。您需要更好地控制“functionMap”。 - Taha Paksu
显示剩余3条评论

16
在这个解决方案中,我扩展了 DOM 以具有 onoff 方法,并能够使用事件命名空间:

var events = {
  on(event, cb, opts){
    if( !this.namespaces ) // save the namespaces on the DOM element itself
      this.namespaces = {};

    this.namespaces[event] = cb;
    var options = opts || false;
    
    this.addEventListener(event.split('.')[0], cb, options);
    return this;
  },
  off(event) {
    this.removeEventListener(event.split('.')[0], this.namespaces[event]);
    delete this.namespaces[event];
    return this;
  }
}

// Extend the DOM with these above custom methods
window.on = Element.prototype.on = events.on;
window.off = Element.prototype.off = events.off;


window
  .on('mousedown.foo', ()=> console.log("namespaced event will be removed after 3s"))
  .on('mousedown.bar', ()=> console.log("event will NOT be removed"))
  .on('mousedown.baz', ()=> console.log("event will fire once"), {once: true});

// after 3 seconds remove the event with `foo` namespace
setTimeout(function(){
    window.off('mousedown.foo')
}, 3000)
Click anywhere 


2
我点赞了这个评论,但是在DOM中存储JS对象引用会有一些注意事项,如果你不知道该做什么,这可能会导致内存泄漏。例如,如果您从DOM中删除其中一个元素,而它仍然存储对事件处理程序的其他引用,则可能会发生这种情况。只需确保在打算删除该元素时调用其'off'方法即可。我认为大多数(?)现代(?)垃圾收集器已经智能化解决了这个问题,但肯定还存在一些(尤其是旧的)浏览器没有解决。 - brennanyoung
你被踩了,因为你没有提供一种将 addEventListener 添加到 DOM 元素的方法。看起来你是在 window 上添加了一个全局事件监听器。这不是 OP 所要求的。 - Craig
1
@Craig - 是的,我做了。看代码 :) 查看演示,其中包含DOM元素(按钮)的相同代码。 - vsync

11

我认为您正在寻找 addEventListenerremoveEventListener。 您还可以定义自定义事件并使用 dispatchEvent 进行触发。

但是,要删除事件侦听器,您将需要保留对事件函数的引用,以便仅删除您想要删除的功能,而不是清除整个事件。


没错。我太专注于在命名空间中放置事件处理程序,没有查看removeEventListener的文档。谢谢你。 - Tyler Conover
21
这如何帮助对事件进行命名空间划分?您能否举个例子,说明如何在 window 上对 mouseup 事件进行命名空间划分,然后只删除其中一个(假设还有其他未命名空间的事件)? - vsync
1
但是如果我没有要删除事件侦听器的函数的引用怎么办?在jQuery中,命名空间非常方便。 - Parth
@vsync 请看下面我的回答。 - Andrew

2
创建事件组(命名空间)的方法:
  1. AbortController.signal传递给addEventListener API中'options'参数中的'signal'属性。
  2. 将组内所有事件附加到单独的HTMLElement上。
  3. 为每个订阅缓存事件类型、监听器和useCapture。
删除组内所有事件:
  1. 调用AbortController.abort():所有使用此AbortController实例的“signal”配置的事件将被删除。
  2. 移除HTMLElement:这将销毁与HTMLElement关联的所有事件侦听器。
  3. 通过传递事件的类型、侦听器和useCapture配置来使用removeEventListener

注意:第二种方法需要为每个组EventTarget(HTMLElement)分派事件。其他方法允许单个EventTarget用于事件订阅和分派。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接