如何删除事件处理程序的绑定版本?

How does one remove the bound version of an event handler?

我的绑定事件有问题,我想在触发更改事件时删除每个事件。没有循环它工作正常。

this.objectInstance.on('selected', () => {
    document.querySelectorAll('select').forEach((el) => {
        this.test = this.fontFamilyHandler.bind(this, el.getAttribute('id'));
        el.addEventListener('change', this.test);
    }) 
});

this.objectInstance.on('deselected', () => {
    document.querySelectorAll('select').forEach((el) => {
        el.removeEventListener('change', this.test);
    })
});

fontFamilyHandler = (key, evt) => {
    this.objectInstance.set(key, evt.target.value);
    this.canvas.requestRenderAll();
}

你知道为什么它不起作用吗?

在每次迭代中,您都会用下一个侦听器覆盖 this.test。所以最后,this.test 将包含 附加的最后一个侦听器 ,因此它只能删除那个。

我可以在这里看到两个解决方案:

  1. 您可以保存 所有 监听器函数,然后再次删除它们,将元素映射到监听器:
this.objectInstance.on('selected', () => {
    this.test = new WeakMap();
    document.querySelectorAll('select').forEach((el) => {
        const handler = this.fontFamilyHandler.bind(this, el.getAttribute('id'));
        this.test.set(el, handler);
        el.addEventListener('change', handler);
    }) 
});

this.objectInstance.on('deselected', () => {
    document.querySelectorAll('select').forEach((el) => {
        if (!this.test.has(el)) continue;
        el.removeEventListener('change', this.test.get(el));
    })
});

(注意:仅使用数组并不稳定,因为如果文档中 <select> 个元素的数量发生变化,索引将不同步。)

(注意:它也适用于 Map 而不是 WeakMap,但后者允许更早地对从 [=55 中删除的任何 <select> 元素进行垃圾回收=].)

  1. 对所有元素使用相同的函数,而不是每次都绑定。由于您访问的是与监听器相同的元素,它是一个 <select>,这意味着 change 事件只会来自 <select> 本身,因为它不能有任何子元素能够发送 change 事件,使用 event.target.id 也可以:
const eventHandler = event => this.fontFamilyHandler(event.target.id, event);

this.objectInstance.on('selected', () => {
    document.querySelectorAll('select').forEach((el) => {
        el.addEventListener('change', eventHandler);
    }) 
});

this.objectInstance.on('deselected', () => {
    document.querySelectorAll('select').forEach((el) => {
        el.removeEventListener('change', eventHandler);
    })
});

(注意:el.idel.getAttribute('id') 更容易,而且效果相同 - 参见 Element#id。)

  1. 或者,您可以直接使用 fontFamilyHandler 并将其更改为从元素本身读取 ID。
this.objectInstance.on('selected', () => {
    document.querySelectorAll('select').forEach((el) => {
        el.addEventListener('change', this.fontFamilyHandler);
    }) 
});

this.objectInstance.on('deselected', () => {
    document.querySelectorAll('select').forEach((el) => {
        el.removeEventListener('change', this.fontFamilyHandler);
    })
});

// I removed the `key` argument here
this.fontFamilyHandler = evt => {
    this.objectInstance.set(evt.target.id, evt.target.value);
    this.canvas.requestRenderAll();
}

从上面的评论...

"One needs to see surrounding code in order to get an understanding of the actual this the OP is dealing with ... I'm especially curious about fontFamilyHandler = (key, evt) => { /* ... */ } where this function floats freely versus line 3 where all of a sudden one sees this.fontFamilyHandler.bind(this, el.getAttribute('id'));"

在那之前,下一个提供的答案假定了一种 class syntax/system 的假设 MyType class.

当然是因为绑定...

this.fontFamilyHandler.bind(this, el.getAttribute('id'));

... 根本没有必要,因为 OP 已经访问了此处理程序中的 event.target,它等于上面的 el ...

fontFamilyHandler = (key, evt) => {
  this.objectInstance.set(key, evt.target.value);
  this.canvas.requestRenderAll();
}

因此,绑定每个元素的 id 是不必要的,因为这个 属性 可以通过 evt.target.id 访问,就像已经通过 evt.target.value.[=23 访问的值一样多=]

最后,代码将归结为更具可读性的内容,例如...

//class MyType {
//  constructor() {
//    this.objectInstance = { on: () => {} };

    this.objectInstance.on('selected', () => document
      .querySelectorAll('select')
      .forEach(el =>
        el.addEventListener('change', this.fontFamilyHandler)
      )
    );
    this.objectInstance.on('deselected', () => document
      .querySelectorAll('select')
      .forEach(el =>
        el.removeEventListener('change', this.fontFamilyHandler)
      )
    );

    this.fontFamilyHandler = ({ target }) => {
      this.objectInstance.set(target.id, target.value);
      this.canvas.requestRenderAll();
    };

//  }
//}