简体   繁体   中英

ES6 Class - Call method from within event handler

I am trying to write an ES6 class for an interactive calendar on my current project.

The Class looks similar to the below:

class Calendar {

constructor (s) {

    this.eventButtons = s.eventButtons;
    this.eventButtons.forEach(button => button.addEventListener('click', this.method1);
    this.eventBoxes = s.eventBoxes;


method1 (e) {
    e.preventDefault();
    this.method2(e.target.href);
}

method2 (url) {
    console.log(url);
}

}


export default Calendar;

I know that the context of the 'this' keyword has changed from the constructor to the button which has been clicked within the method1 function. However I don't know how to keep the context of the button and the constructor within the same function. I tried changing the button event listener code to the following:

this.eventButtons.forEach(button => button.addEventListener('click', this.method1).bind(this);

But this just switches the context of the 'this' keyword to the constructor rather than the button. I need to use both in my function.

Any ideas? I'm hoping this is quite a common issue?

You could create a closure that would send the event and the button. The closure would keep the this context and send the button as well

button => button.addEventListener('click', event => this.method1(event, button))

Since you are using ES6, have you tried using an arrow function ?

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

method1 = (e) => {
    e.preventDefault();
    this.method2(e.target.href);
}

You have a few options:

You can bind the methods themselves:

this.method1 = this.method1.bind(this);
this.method2 = this.method2.bind(this);

There's the bind operator if you're using Babel (or some other transpiler). It hasn't been accepted into the standard yet, so I'd be weary of using it. Using the bind operator, you can do the equivalent:

this.method1 = ::this.method1
this.method2 = ::this.method2

The other option is to do what you've done already, just corrected.

You have to bind the method, not the result of the forEach.

this.eventButtons.forEach(button =>
    button.addEventListener('click', this.method1.bind(this)));

or with bind op:

this.eventButtons.forEach(button =>
    button.addEventListener('click', ::this.method1));

Finally, you also have the option of creating a wrapper function using arrow notation for lexical scope:

this.eventButtons.forEach(button =>
    button.addEventListener('click', (...params) => this.method1(...params)));

And if you use ES6, you can also use for of instead of forEach. This prevents creating yet another callback with its own scope. In this code, the keyword 'this' still refers to the original class.

this.eventButtons = s.eventButtons;
for(b of this.eventButtons){
   b.addEventListener('click', () => this.method1);
}

Try using a lambda expression to set the delegate of your event as well. Something like below:

button.addEventListener('click', (e) => { e.preventDefault(); this.method2(); });

You can use bind to create a partial function:

this.eventButtons.forEach(button => button.addEventListener('click', this.method1.bind(this, button));

It works assuming you change method1 to be:

method1 (button, e) {
  e.preventDefault();
  this.method2(e.target.href);
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM