使用addEventListener是否可以调用类方法?

23

我一直在想,.addEventListener 方法中的第二个参数,是否可以调用“(自定义)类方法”,而不是函数?

比如以下代码会起作用吗?

var object = new ClassName();
document.getElementById('x').addEventListener('click', object.method, false);

3
取决于您所指的“工作”的含义。您尝试过它吗?method是如何定义的?它需要做什么?它可能会“工作”,也可能不会。 - cookie monster
这段代码只是我脑海中的想法,我还没有测试过。我以前从未接触过JavaScript中的面向对象编程,所以这只是我在思考的一个问题。 - Azrael
那么答案是“可能”。如果.method确实是一个函数,它将作为处理程序传递,但是不知道它的功能,它可能会或可能不会“工作”。 - cookie monster
请注意,“自定义类方法”这种东西是不存在的。object.method仍然是一个普通函数,对于addEventListener来说它看起来就像任何其他函数一样。函数与被分配为属性值的对象之间没有隐含的关系。假设你有function foo() {...}; obj.method = foo; obj2.method2 = foo; var bar = foo;。那么,在你的逻辑之后,foo现在是什么?它“属于”什么? - Felix Kling
通过“自定义类方法”,我指的是除内置方法以外的任何内容。因此加上了引号。如果这让您感到困惑,我很抱歉。 - Azrael
糟糕,刚意识到我没有使用引号。 - Azrael
8个回答

31

不行,你写的代码是错误的,因为在调用method时没有将object作为上下文来执行。在method内部,this会被设置为触发事件的DOM元素。

如果你想要调用方法并保留上下文,可以使用一个函数来封闭object变量:

var object = new ClassName();
document.getElementById('x').addEventListener('click', function () {
  object.method()
}, false);

1
+1 你可能已经知道了,但为了OP的利益,我还是要评论一下。this不是“上下文”,而是执行上下文的一个参数。对于函数来说,它是由函数的调用方式(或使用bind)设置的。它可以设置为任何对象(在严格模式下甚至可以设置为任何值)。 - RobG
5
那么,你如何移除同一个事件监听器?这是我现在面临的挑战。 - Giovanni
4
这个链接解决了我的问题:https://dev59.com/AGkw5IYBdhLWcg3wrsnn - Giovanni

18

我刚刚尝试了一种更直接的方法,它似乎有效,并且我认为它非常简洁。我只是将bind(this)添加到addEventListener()的参数中。不要在不需要时使它变得复杂...

class HTMLExample extends HTMLElement{
  constructor(){
    super();
    this.count = 0;
    this.addEventListener('click', this.onClick.bind(this), false);        
  }
  
  onClick(event){
    let prevCount = this.count;
    this.count++;    
    let msg = `Increased Count from ${prevCount} to ${this.count}`;
    document.getElementById("log").innerHTML += `${msg}<br/>`;     
  }
}

3

可以通过绑定来实现。

下面我正在创建一个Web组件。这是大多数浏览器支持的功能。

为了创建这个Web组件,我正在创建一个扩展HTMLElement类的类。就JavaScript而言,这只是另一个常规类。

现在,我希望我的Web组件表现得像一个按钮。 每次单击我的标签时,我希望计数器增加。

注意:三个点表示“更多代码”在那里。

因此,我在构造函数中创建一个将保存计数的变量:

constructor(){
  ...
  this.count = 0;
  ...

接下来,我会添加一个监听器来检测当我的 Web 组件被点击时,并调用 onClick 方法:

constructor(){
  ...
  this.count = 0;
  ...
  this.addEventListener('click', this.onClick, false);        
}

onClick(event){
 let prevCount = this.count;
 this.count++;    
 let msg = `Increased Count from ${prevCount} to ${this.count}`;
 document.getElementById("log").innerHTML += `${msg}<br/>`;     
 }

onClick旨在增加计数器并打印计数器的上一个和当前值; 但是,这里存在问题!!!

问题出在这一行this.count++;。关键字this没有引用HTMLExample类的实例。原因是使用addEventListener添加的函数将把绑定元素作为this引用,而不是函数或对象。

因此,您必须将由addEventListener使用的该函数绑定到您的类实例上,方法是添加以下代码:

this.onClick = this.onClick.bind(this);

现在,当你在方法 onClick 中使用关键词 this 时,this 将会引用类 HTMLExample 的实例。请查看下面的代码并运行它,我认为这样会更加清晰:

class HTMLExample extends HTMLElement{
  constructor(){
    super();
    this.count = 0;
    this.onClick = this.onClick.bind(this);
    this.addEventListener('click', this.onClick, false);        
  }
  
  onClick(event){
    let prevCount = this.count;
    this.count++;    
    let msg = `Increased Count from ${prevCount} to ${this.count}`;
    document.getElementById("log").innerHTML += `${msg}<br/>`;     
  }
}
customElements.define('example-component', HTMLExample);
example-component{
  cursor: pointer;
  border: 1px solid black;
  background: lightgray;
  padding: 2px;
}
<example-component>Button</example-component>
<div id="log"></div>


2

大多数答案使用Function.prototype.bind(),但你可以通过简单编写代码来实现。

class EventHandler {
  constructor(element) {
    element.addEventListener('mousedown', this.handler); 
  }
  // just declare the method as a property with arrow function.
  hander = () => {
    console.log(this);
  }
}

我在codepen.io上编写了完整的可工作代码示例。


这个效果很好,谢谢! - undefined

1
这里的答案很有帮助,但仍让我想知道是否有一个整洁的语法来做基本相同的事情。我不想把类实例的引用传递给它自己,那太奇怪了。
这是一个整洁的模式,将所有内容都保留在类内部,而不需要使用实例引用,而是使用闭包。
myclass= function(){};
myclass.prototype.addSystemEvents = function(){
  var scope = this;
  window.addEventListener( 'resize', function(){scope.onWindowResize();}, false);
}
myclass.prototype.onWindowResize = function(){
   //do whatever
}

1
在你的类中: window.addEventListener("keydown",(e)=>this.keyDown(e)); - Marius

1

是的,这是可能的。您需要调用该方法并保留上下文,在函数中使用闭包对象变量:

var object = new ClassName();
document.getElementById('x').addEventListener('click', function (e) {
  object.method(e)
}, false);

如果在函数内部没有调用该函数,'this'将被设置为触发事件的DOM元素。

请问您能否解释一下,当我们使用函数封闭对象变量时,上下文是如何改变的?之前它会将上下文作为触发事件的DOM元素发送,但是使用函数封闭后会改变上下文,但是具体是如何改变的呢? - Ram Shankar Kumar

0

是的,这是可能的,只需要注意上下文。例如:

ClassName.prototype.click_handler = function(el) {
    // Here 'this' doesn't refer to the instance
    // of ClassName but to the element clicked
}
var object = new ClassName();

document.getElementById('x').addEventListener('click', object.click_handler, false);

@rvighne:你对这个答案做了一个相当激进的改变。 - cookie monster
@cookiemonster 不完全是。原始答案本意良好,但它会抛出JavaScript错误(您无法设置实例的原型)。显然我们不能这样做。我相信这样做可以实现Sergio的意图。 - rvighne
好的。这彻底改变了答案的意思,使它变成完全不同的东西。 - cookie monster
@rvighne 感谢你的编辑建议。我真不知道当时在想什么呢! - S. A.

0

对于使用 TypeScript 或 Babel/Transpilation 的开发者,以下是我能想到的最干净的方法,当然还有改进的空间!

class Person {
    name = "bobby"
    sayGoodbyBound = this.sayGoodby.bind(this);
    sayGoodby() {
        console.log('Goodbye, my name is', this.name);
    }
}

const person = new Person();
let element = document.createElement("button")
element.addEventListener("click", person.sayGoodbyBound);

element.click()

// remove same one
element.removeEventListener("click", person.sayGoodbyBound);


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