删除 class 中定义的 EventListener
Remove EventListener defined in a class
我正在尝试删除一个 eventListener,但我似乎遗漏了什么。
为什么下面的代码不起作用,它没有从按钮中删除事件侦听器。
我也试过绑定这个来传递作用域,但是也没用
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
OP 的代码不起作用有两个原因。
- 在一种情况下,原型
eventHandler
错过了正确的 this
上下文。
- 对于 运行
this.eventHandler.bind(this)
的第二种情况,创建了一个新的(处理程序)函数,但没有保存对它的引用。因此 removeEventHandler
永远不会引用正确的事件处理程序。
可能的解决方案...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
另一种可能的方法是使用基于箭头函数的、因此特定于实例的事件处理程序。箭头函数不支持显式 this
绑定。他们总是参考他们实施的上下文。
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
然而,这两种方法都表明,引用特定事件处理程序的原型实现不是应该遵循的路径。
对于 OP 提供的场景,我会选择第一个解决方案,因为它提供了本地实现的代码重用 handleTestClickEvent
。它还带有关于实例特定 this.eventHandler
的较小足迹,前者是从 handleTestClickEvent.bind(this)
创建的,而第二个解决方案为每个实例提供完整的处理程序实现。
您的代码看起来太复杂了。如果你想禁用点击处理程序,有几种方法可以做到这一点而无需删除处理程序。 Event delegation 让您在这里的生活也更轻松。在代码段中,button#b
在点击 3 次后被禁用,使用数据属性。 button#a
可以无限点击
document.addEventListener("click", handle);
function handle(evt) {
if (evt.target.id === "b" && +(evt.target.dataset.nclicks || 1) < 3) {
const nClicks = +(evt.target.dataset.nclicks || 0);
evt.target.dataset.nclicks = nClicks + 1;
if (nClicks === 2) {
evt.target.setAttribute("disabled", "disabled");
}
return console.log(`you clicked button#b${
nClicks === 2 ? ", no more clicks 4u" : ""}`);
}
if (evt.target.id === "a") {
console.clear();
return console.log(`you clicked button#a on ${new Date().toLocaleString()}`);
}
}
<button id="a">click me a</button>
<button id="b">click me b</button>
作为事件处理程序的原型方法有点问题,特别是当您同时需要绑定到实例的 this 值和对实际事件处理程序函数的引用时。
默认情况下,事件队列在事件绑定到的元素的上下文中调用处理程序。更改上下文很容易,但这会让您创建一个新函数,然后将其用作事件处理程序,并且该函数不再是原型中的方法。
如果要保持紧凑的class结构,一种方法是将事件处理程序方法定义为实例自己的属性,它们根本无法被继承。最简单的方法是在构造函数中将方法定义为箭头函数。
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
箭头函数保留对其定义的词法环境的引用,事件队列不能覆盖上下文。现在,处理函数中的 this
已正确绑定到实例,并且 this.eventHandler
引用附加到事件的函数。
在创建自己的 属性 时使用 bind
内存消耗稍少的选项,如下所示:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
这里bind
创建一个新的函数对象,然后调用原型中的方法,方法的实际代码没有重复。如果您写道:
,则大致相似
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
值得注意的是,当用底层原型 属性 具有的相同名称定义自己的 属性 时,原型 属性 不会被覆盖,它仅在实例中被隐藏, class 的多个实例仍将按预期工作。
另一种选择是创建您自己的“事件模型”,它为所有事件创建一个包装函数(如上面最后一个代码示例),并存储对该函数的引用。包装器使用 call
调用实际处理程序,它可以将所需的 this
值绑定到事件处理程序。存储的函数引用用于删除事件。构建这样一个 model 并不是非常复杂,但它提供了一些关于 this
绑定和本机事件模型如何工作的知识。
我正在尝试删除一个 eventListener,但我似乎遗漏了什么。
为什么下面的代码不起作用,它没有从按钮中删除事件侦听器。
我也试过绑定这个来传递作用域,但是也没用
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
OP 的代码不起作用有两个原因。
- 在一种情况下,原型
eventHandler
错过了正确的this
上下文。 - 对于 运行
this.eventHandler.bind(this)
的第二种情况,创建了一个新的(处理程序)函数,但没有保存对它的引用。因此removeEventHandler
永远不会引用正确的事件处理程序。
可能的解决方案...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
另一种可能的方法是使用基于箭头函数的、因此特定于实例的事件处理程序。箭头函数不支持显式 this
绑定。他们总是参考他们实施的上下文。
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
然而,这两种方法都表明,引用特定事件处理程序的原型实现不是应该遵循的路径。
对于 OP 提供的场景,我会选择第一个解决方案,因为它提供了本地实现的代码重用 handleTestClickEvent
。它还带有关于实例特定 this.eventHandler
的较小足迹,前者是从 handleTestClickEvent.bind(this)
创建的,而第二个解决方案为每个实例提供完整的处理程序实现。
您的代码看起来太复杂了。如果你想禁用点击处理程序,有几种方法可以做到这一点而无需删除处理程序。 Event delegation 让您在这里的生活也更轻松。在代码段中,button#b
在点击 3 次后被禁用,使用数据属性。 button#a
可以无限点击
document.addEventListener("click", handle);
function handle(evt) {
if (evt.target.id === "b" && +(evt.target.dataset.nclicks || 1) < 3) {
const nClicks = +(evt.target.dataset.nclicks || 0);
evt.target.dataset.nclicks = nClicks + 1;
if (nClicks === 2) {
evt.target.setAttribute("disabled", "disabled");
}
return console.log(`you clicked button#b${
nClicks === 2 ? ", no more clicks 4u" : ""}`);
}
if (evt.target.id === "a") {
console.clear();
return console.log(`you clicked button#a on ${new Date().toLocaleString()}`);
}
}
<button id="a">click me a</button>
<button id="b">click me b</button>
作为事件处理程序的原型方法有点问题,特别是当您同时需要绑定到实例的 this 值和对实际事件处理程序函数的引用时。
默认情况下,事件队列在事件绑定到的元素的上下文中调用处理程序。更改上下文很容易,但这会让您创建一个新函数,然后将其用作事件处理程序,并且该函数不再是原型中的方法。
如果要保持紧凑的class结构,一种方法是将事件处理程序方法定义为实例自己的属性,它们根本无法被继承。最简单的方法是在构造函数中将方法定义为箭头函数。
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
箭头函数保留对其定义的词法环境的引用,事件队列不能覆盖上下文。现在,处理函数中的 this
已正确绑定到实例,并且 this.eventHandler
引用附加到事件的函数。
在创建自己的 属性 时使用 bind
内存消耗稍少的选项,如下所示:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
这里bind
创建一个新的函数对象,然后调用原型中的方法,方法的实际代码没有重复。如果您写道:
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
值得注意的是,当用底层原型 属性 具有的相同名称定义自己的 属性 时,原型 属性 不会被覆盖,它仅在实例中被隐藏, class 的多个实例仍将按预期工作。
另一种选择是创建您自己的“事件模型”,它为所有事件创建一个包装函数(如上面最后一个代码示例),并存储对该函数的引用。包装器使用 call
调用实际处理程序,它可以将所需的 this
值绑定到事件处理程序。存储的函数引用用于删除事件。构建这样一个 model 并不是非常复杂,但它提供了一些关于 this
绑定和本机事件模型如何工作的知识。