【问题标题】:How to removeEventListener that is addEventListener with anonymous function?如何删除带有匿名函数的 addEventListener 的EventListener?
【发布时间】:2011-08-05 07:57:56
【问题描述】:
function doSomethingWith(param)
{
    document.body.addEventListener(
        'scroll',
        function()
        {
            document.write(param);
        },
        false
    ); // An event that I want to remove later
}
setTimeout(
    function()
    {
        document.body.removeEventListener('scroll', HANDLER ,false);
            // What HANDLER should I specify to remove the anonymous handler above?
    },
    3000
);
doSomethingWith('Test. ');

【问题讨论】:

标签: javascript event-handling anonymous-function


【解决方案1】:

你不能。您必须使用命名函数或以某种方式存储引用。

var handler;

function doSomethingWith(param) {
    handler = function(){
        document.write(param);
    };  
    document.body.addEventListener('scroll', handler,false);
}
setTimeout(function() {
     document.body.removeEventListener('scroll', handler ,false);
}, 3000);

最好以结构化的方式执行此操作,以便您可以识别不同的处理程序并将其删除。在上面的示例中,您显然只能删除最后一个处理程序。

更新:

您可以创建自己的处理程序处理程序 (:)):

var Handler = (function(){
    var i = 1,
        listeners = {};

    return {
        addListener: function(element, event, handler, capture) {
            element.addEventListener(event, handler, capture);
            listeners[i] = {element: element, 
                             event: event, 
                             handler: handler, 
                             capture: capture};
            return i++;
        },
        removeListener: function(id) {
            if(id in listeners) {
                var h = listeners[id];
                h.element.removeEventListener(h.event, h.handler, h.capture);
                delete listeners[id];
            }
        }
    };
}());

然后你可以使用它:

function doSomethingWith(param) {
    return Handler.addListener(document.body, 'scroll', function() {
        document.write(param);
    }, false);
}

var handler = doSomethingWith('Test. ');

setTimeout(function() {
     Handler.removeListener(handler);
}, 3000);

DEMO

【讨论】:

  • 您能解释一下结构化方式是什么吗?我的英语水平不够好,无法理解……谢谢。
  • @Japboy:不客气 :) 我刚刚注意到一个小错误并修复了它。
  • 为什么要包装 addListener 和 removeListener?
  • @useless:你是说自调用函数吗?将ilisteners 保持为“私有”。
  • @Bergi:完成。如果我错过了什么,请告诉我。
【解决方案2】:

你不能,你需要一个函数的引用:

function doSomethingWith(param) {
   var fn = function(){ document.write(param); };
   document.body.addEventListener('scroll', fn, false);
   setTimeout(function(){ document.body.removeEventListener('scroll', fn, false); }, 3000);
}
doSomethingWith('Test. ');

【讨论】:

  • 你如何传递事件对象?
  • @slier var fn = function(event){ document.write(param); };
【解决方案3】:

你也可以这样做:

const ownAddEventListener = (scope, type, handler, capture) => {
  scope.addEventListener(type, handler, capture);
  return () => {
    scope.removeEventListener(type, handler, capture);    
  }
}

然后你可以像这样移除事件监听器:

// Add event listener
const disposer = ownAddEventListener(document.body, 'scroll', () => { 
  // do something
}, false);

// Remove event listener
disposer();

【讨论】:

  • 这样是否可以确定对象上绑定了哪些事件?
  • 你可以给函数添加一些属性,比如类型、范围等。const disposerFn = () => { scope.removeEventListener(type, handler, capture); }disposerFn.type = type;return disposerFn;
【解决方案4】:

如果你不需要支持IE,你可以使用一次选项

[Element].addEventListener('click', () => {...}, {
  capture: false,
  once: true
});

【讨论】:

    【解决方案5】:

    这显着改善了@FelixKling 的顶级答案。改进如下:

    (1) 与 MDN docs 对齐,指的是 type、listener、useCapture

    (2) 移除旧式 IIFE 并采用基于类的方法

    (3) 新增addEventListenerById 方法,支持使用自己的自定义ID(避免从返回值中检索ID)

    (4)removeEventListener方法返回已移除监听的ID或null

    (5) 新增length方法返回活跃监听数

    (6) 新增 SO 代码 sn-p(证明有效)

    代码如下:

    class Listeners {
    
      #listeners = {} // # in a JS class signifies private 
      #idx = 1
    
      // add event listener, returns integer ID of new listener
      addEventListener(element, type, listener, useCapture = false) {
        this.#privateAddEventListener(element, this.#idx, type, listener, useCapture)
        return this.#idx++
      }
      
      // add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
      addEventListenerById(element, id, type, listener, useCapture = false) {
        this.#privateAddEventListener(element, id, type, listener, useCapture)
        return id
      }
    
      #privateAddEventListener(element, id, type, listener, useCapture) {
        if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
        element.addEventListener(type, listener, useCapture)
        this.#listeners[id] = {element, type, listener, useCapture}
      }
    
      // remove event listener with given ID, returns ID of removed listener or null (if listener with given ID does not exist)
      removeEventListener(id) {
        const listen = this.#listeners[id]
        if (listen) {
          listen.element.removeEventListener(listen.type, listen.listener, listen.useCapture)
          delete this.#listeners[id]
        }
        return !!listen ? id : null
      }
    
      // returns number of events listeners
      length() {
        return Object.keys(this.#listeners).length
      }
    }
    
    // For demo purposes only, a button to which the click listeners will be attached
    const testBtn = document.querySelector('.test-button')
    
    // Usage
    const listeners = new Listeners() // in a modular environment ... export const listeners = new Listeners()
    
    const listener1 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #1', e.offsetX, e.offsetY))
    const listener2 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #2', e.offsetX, e.offsetY))
    listeners.addEventListenerById(testBtn, 'listener-id', 'click', e => console.log('hello from click listener #listener-id', e.offsetX, e.offsetY))
    
    // Click function for the 3 remove listener buttons (not for the testBtn to which the listeners are attached)
    const onRemoveClick = (id) => {
      const removed = listeners.removeEventListener(id)
      if (removed == null) {
        console.log(`cannot remove listener #${id} (does not exist)`)
      } else {
        console.log(`ID of removed listener #${removed}`)
      }
      const listenersCount = listeners.length()
      console.log(`there are ${listenersCount} listeners still listening`)
    }
    .test-button {
      width: 35%;
      height: 40px;
      float: left;
    }
    
    .remove-listener-button {
      width: 15%;
      height: 40px;
      float: left;
    }
    <button class="test-button">click to prove multiple listeners are listening to my click</button>
    
    <button class="remove-listener-button" onclick="onRemoveClick(1)">remove listener #1</button>
    <button class="remove-listener-button" onclick="onRemoveClick(2)">remove listener #2</button>
    <button class="remove-listener-button" onclick="onRemoveClick('listener-id')">remove #listener-id</button>

    适合打字爱好者

    在 TypeScript 中也是如此,对上述 JS 版本进行了一些增强:

    (1) removeEventListener 方法返回已移除侦听器的详细信息(包括 ID),而不是仅返回已移除侦听器的 ID

    (2) 新增ids 方法,返回所有活动监听器的ID

    (3) 添加了 3 个方法 addEventListeners addEventListenersByIds removeEventListeners 允许您在一次调用中添加/删除多个侦听器(参见下面的使用示例)

    (4) 新增removeAllEventListenersdestroy 清理方法(本质上这2个是一样的,但后者不返回值)

    这是 TypeScript 代码(只需复制并粘贴到新的 .ts 文件中):

    interface IInternalListener {
      element: HTMLElement
      id: string
      type: string
      listener: EventListenerOrEventListenerObject
      useCapture: boolean
    }
    
    export interface IListener extends Omit<IInternalListener, 'id'> {
      id: string | number
    }
    
    class Listeners {
    
      #listeners: { [key: string]: IInternalListener } = {}
      #idx = 1 // # in a JS class signifies private
    
      // add event listener, returns integer ID of new listener
      addEventListener(element: HTMLElement, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): number {
        this.#privateAddEventListener(element, this.#idx.toString(), type, listener, useCapture)
        return this.#idx++
      }
    
      addEventListeners(element: HTMLElement, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): number[] {
        const returnIds: number[] = []
        types.forEach((type: string) => {
          const returnId: number = this.addEventListener(element, type, listener, useCapture)
          returnIds.push(returnId)
        })
        return returnIds
      }
    
      // add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
      addEventListenerById(element: HTMLElement, id: string | number, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): string | number { // eslint-disable-line max-len
        return this.#privateAddEventListener(element, id.toString(), type, listener, useCapture)
      }
    
      addEventListenersByIds(element: HTMLElement, ids: Array<string | number>, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): Array<string | number> { // eslint-disable-line max-len
        const returnIds: Array<string | number> = []
        if (ids.length !== types.length) throw Error(`Cannot add ${types.length} event listeners using ${ids.length} ids - ids and types must be of equal length`)
        types.forEach((type: string, idx: number) => {
          const id: string | number = ids[idx]
          const returnId: string | number = this.addEventListenerById(element, id, type, listener, useCapture)
          returnIds.push(returnId)
        })
        return returnIds
      }
    
      // remove event listener with given ID, returns removed listener or null (if listener with given ID does not exist)
      removeEventListener(id: string | number): IListener | null {
        const strId: string = id.toString()
        const internalListener: IInternalListener = this.#listeners[strId]
        if (internalListener) {
          internalListener.element.removeEventListener(internalListener.type, internalListener.listener, internalListener.useCapture)
          const listener: IListener = this.#privateGetListener(internalListener)
          delete this.#listeners[strId]
          return listener
        }
        return null
      }
    
      // noinspection JSUnusedGlobalSymbols
      removeEventListeners(ids: Array<string | number>): Array<IListener | null> {
        const returnListeners: Array<IListener | null> = []
        ids.forEach((id: string | number) => {
          const returnListener: IListener | null = this.removeEventListener(id)
          returnListeners.push(returnListener)
        })
        return returnListeners
      }
    
      // removes all event listeners and resets idx
      removeAllEventListeners(): Array<IListener | null> {
        const ids: Array<string | number> = this.ids()
        const returnListeners: Array<IListener | null> = this.removeEventListeners(ids)
        this.#idx = 1
        return returnListeners
      }
    
      // same as removeAllEventListeners but no return value
      destroy(): void {
        this.removeAllEventListeners()
      }
    
      // returns ids of events listeners
      ids(): Array<string | number> {
        const ids: string[] = Object.keys(this.#listeners)
        return ids.map(id => this.#privateExternalId(id))
      }
    
      // returns number of events listeners
      length(): number {
        return Object.keys(this.#listeners).length
      }
    
      #privateAddEventListener(element: HTMLElement, id: string, type: string, listener: EventListenerOrEventListenerObject, useCapture: boolean): string | number {
        if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
        element.addEventListener(type, listener, useCapture)
        this.#listeners[id] = { id, element, type, listener, useCapture }
        return this.#privateExternalId(id)
      }
    
      #privateGetListener(listener: IInternalListener): IListener {
        return {
          ...listener,
          id: this.#privateExternalId(listener.id)
        }
      }
    
      #privateExternalId(id: string): string | number {
        const idIsInteger = /^\d+$/.test(id)
        if (idIsInteger) return parseInt(id, 10)
        return id
      }
    }
    
    export const listeners: Listeners = new Listeners()
    

    和 TypeScript 的用法:

    import { listeners, IListener } from './your-path/listeners'
    
    // Add and remove listener
    const listenerId: number = listeners.addEventListener(mainVideo, 'timeupdate', (evt: Event) => console.log('hi', evt))
    listeners.removeEventListener(listenerId)
    
    // Add and remove listener by custom ID
    listeners.addEventListenerById(mainVideo, 'custom-id', 'timeupdate', (evt: Event) => console.log('hello', evt))
    listeners.removeEventListener('custom-id')
    
    // Log id of all active listeners
    console.log(listeners.ids())
    
    // Log active listeners count
    console.log(listeners.length())
    
    // Get details of removed listener
    listeners.addEventListenerById(mainVideo, 'fred', 'timeupdate', (evt: Event) => console.log('bye', evt))
    const removedListener: IListener | null = listeners.removeEventListener('fred')
    console.log('removed listener was', removedListener)
    
    // clean up
    const removedListeners: Array<IListener | null> = listeners.removeAllEventListeners()
    console.log('removed listeners were', removedListeners)
    
    // simple quick clean up
    listeners.destroy()
    

    在一次调用中添加/删除多个侦听器的高级 TypeScript 用法:

    // Add multiple event listeners
    const listenerIds: number[] = listeners.addEventListeners(this.video, ['timeupdate', 'seeking', 'pause', 'play', 'playing'], (evt: Event) => {
      const target = evt.target as HTMLVideoElement
      this.currentTime = target.currentTime
    })
    console.log(listenerIds)
    
    // Add multiple event listeners with custom IDs
    listeners.addEventListenersByIds(this.video, ['id-one', 'id-two', 'id-three'], ['timeupdate', 'seeking', 'pause'], (evt: Event) => {
      console.log(evt)
    })
    
    // Remove multiple event listeners
    const removedListeners: Array<IListener | null> = listeners.removeEventListeners([...listenerIds, 'id-two'])
    console.log(removedListeners)
    

    【讨论】:

      猜你喜欢
      • 2018-01-10
      • 1970-01-01
      • 2015-12-01
      • 2010-12-19
      • 2017-04-20
      • 1970-01-01
      • 1970-01-01
      • 2012-04-18
      • 2022-07-11
      相关资源
      最近更新 更多