Получить this контекст класса в связанном методе с сохранением привязки this

#javascript #typescript #dom-events

#javascript #typescript #dom-события

Вопрос:

Я создаю небольшой вспомогательный класс для addEventListener. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу эффективно получить как this контекст класса, так и this контекст события (прикрепленный целевой объект события).

(1) Если я использую функцию со стрелкой, я могу получить this класса, но я не могу получить this, который был привязан (через Function.prototype.call)

(2) Если я использую выражение функции, я могу получить привязку this, но я не могу получить доступ к классу.

(3) Я также не могу использовать внутреннее замыкание. На функцию / метод необходимо ссылаться из внешней области.

Это упрощенный пример, чтобы показать вам, что я имею в виду. Есть ли способ установить все флажки? Все, о чем я мог подумать, это создать еще один вспомогательный класс, который будет инициализироваться для каждого подключенного прослушивателя событий, но это кажется не очень эффективным, если есть более простой способ.

 class EventListenerHelper {
    private targets: Array<EventTarget>
    constructor() {
        // If there was a single target, it would be very easy. But there are multiple
        this.targets = [
            document.body,
            window
        ];
    }

     /**
     * (1) - Try to use an arrow function
     * 
     * This falls short because it's not possible to get the this context of the Event
     */
    private attachWithArrowFunction() {
        this.targets.forEach((target) => {
            target.addEventListener('click', this.listenerCallbackArrow, false);
        });
    }

    private listenerCallbackArrow = (e: Event) => {
        // Cannot get event this
        const eventThis = undefined; 

        // Note that e.target is the innermost element which got hit with the event
        // We are looking for that target that had the event listener attached
        // If I'm not mistaken, the only way to get it is from the this context
        // which is bound to the event callback

        this.processListener(eventThis, e);   
    }

    /**
     * (2) - Try to use a regular class method
     * 
     * This falls short because it's not possible to get the this context of the class
     */
    private attachWithClassMethod() {
        this.targets.forEach((target) => {
            target.addEventListener('click', this.listenerCallbackMethod, false);
        });
    }

    private listenerCallbackMethod(e: Event) {
         // Here we have the eventThis 
        const eventThis = this;

        // But the class instance is completely unreachable
    }

    /**
     * (3) - Try to create a closure wrapper
     * 
     * This almost works, but it's not possible to removeEventListener later
     */
    private attachWithClosure() {
        let self = this;

        this.targets.forEach((target) => {
            target.addEventListener('click', function(e: Event) {
                self.processListener(this as EventTarget, e);
            }, false);
        });
    }


    private processListener(eventThis: EventTarget, e: Event) {
        // Do some stuff with other class methods

        // 

    }

    private detach() {
        this.targets.forEach((target) => {
            target.addEventListener('click', this.listenerCallbackArrow, false);
        });
    }

}
  

Комментарии:

1. Итак, если вам нужны оба контекста, почему бы не назначить переменную — self = this .

2. Это потому, что тогда я не смогу ссылаться на функцию вне замыкания. Пожалуйста, посмотрите пример (3) в коде.

3. Просто. пусть self; Class …. constructor() {self = this ….

4. Это не статический метод. Контекст экземпляра класса будет потерян.

Ответ №1:

Распространенный способ справиться с этим — вернуть функцию:

 private attach() {
  const listener = this.getListener()
  this.targets.forEach(target => {
    target.addEventListener('click', listener, false)
  })
}

private getListener() {
  const self = this
  return function (e: Event) {
    // self if EventListenerHelper this
    // this is Event this
  }
}
  

Но я не вижу в этом особой пользы, потому this что внутри функции, которую вы передаете addEventListener , равно event.currentTarget , так что вы могли бы просто привязать своего слушателя и использовать свойство вместо this :

 constructor() {
  // ...
  this.listener = this.listener.bind(this)
}
private listener(e) {
  // this is EventListenerHelper this
  // e.currentTarget is Event this
}
  

Комментарии:

1. Хм, вы правы насчет currentTarget . Я упустил это из виду ранее, так как при консоли значение было null. зарегистрировал его, но это потому, что он установлен только во время события developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

2. Спасибо за предоставление обоих решений, я, очевидно, буду использовать currentTarget, но полезно знать трюк с возвратом функции