Better way to maintain this reference on event listener functions

Andrea Giammarchi andrea.giammarchi at gmail.com
Mon May 9 16:35:41 UTC 2016


Hi Mark, reading again I think I've got what you meant by different methods
triggered through same `click` listener.
Your example with `this.listener = this.onClick;` made me think you had one
click.

In that case, having a state works pretty well or you could simply have a
list of methods to be triggered when click happens.

```js
class Click {
  constructor(el) {
    this._click = [this.sayHello, this.sayGoodbye];
    el.addEventListener('click', this);
  }
  sayHello() {
    alert('Hello from ' + this.constructor.name);
  }
  sayGoodbye() {
    alert('Goodbye from ' + this.constructor.name);
  }
  handleEvent(e) {
    this._click.forEach((method) => method.call(this, e));
  }
}

// test
new Click(document.documentElement);
```

At this point all you have to do is to push, pop, or splice listeners,
instead of passing through the DOM each time.
You can also copy or pass states around and you'll never need to directly
bind each method.

You can always drop all at once simply doing
`el.removeEventListener('click', this);`

If none of my ideas work for you, then you'll need to come up with some
better proposal or discuss with WHATWG about a different `addEventListener`
signature.

The only thing that could work on top of my head, thanks to recent changes
to the third parameter, is eventually this:

```js
el.addEventListener('click', this.onClick, {context: this});

// with a counterpart
el.removeEventListener('click', this.onClick, {context: this});
```

AFAIK latest changes to the API do not need you to store that third
argument, only its content matters, so that this would be the easiest way
to implement what you're after, yet this is DOM-land, so it's in WHATWG ml
that this should be discussed.

Hope something helped.

Best Regards





On Mon, May 9, 2016 at 5:20 PM, Andrea Giammarchi <
andrea.giammarchi at gmail.com> wrote:

> Hi Mark, not sure I understand. What you are doing is exactly the same,
> there is no difference whatsoever with what I've shown to you.
>
> > Yes but what happens when you have multiple event targets using the
> same `type` of event? You're going to require a lot of extra conditionals
> in `onclick` method. Again this approach is cumbersome and, imo, not the
> most efficient.
>
> conditionals what? the onclick is called only when the node where you
> attached the listener as instance gets invoked. It never triggers in any
> other cases.
> Or better, it is exactly the same as `someNode.addEventListener('click',
> (e) => this.onClick(e))` ... really, PRECISELY the same.
>
> Whenever your `click` gets triggered, the `handleEvent` would behave
> **exactly** the same. Not sure I've stressed the *exactly* part enough so
> nothing is cumbersome, maybe you don't understand or you've never used this
> approach before.
>
> Otherwise, please show me a single example where adding a listener as
> callback, or bound callback, would be triggered differently from adding an
> instance with an inherited, or own, handleEvent.
>
> Going on ...
>
> > 1. When an arrow function is used as the second parameter to
> `addEventListener`, the language can evaluate and use its scoped context
>  ...
>
> This is not going to happen. It's an exception to the arrow that would
> confuse even more about its context.
>
> > 2. Another solution would be if we could maybe pass a method string as
> the second parameter and then an optional context as the fourth parameter
> ...
>
> This is DOM land, since it's about `addEventListener` signature, and I
> would personally vote -1 to that variant.
>
>
> You should really try to understand how `handleEvent` works, IMO. It's the
> sugar you're looking for already since it makes you set any listener you
> want *without* needing to store upfront the bound method: fast, clean,
> simple.
>
> To remove a listener at any time, you don't need to store upfront a bound
> version of the method.
>
> Best Regards
>
>
>
>
> On Mon, May 9, 2016 at 5:05 PM, Mark Kennedy <mkay581 at gmail.com> wrote:
>
>> Yes but what happens when you have multiple event targets using the same
>> `type` of event? You're going to require a lot of extra conditionals in
>> `onclick` method. Again this approach is cumbersome and, imo, not the most
>> efficient.
>>
>> These may not be the best solutions but here are a few options I've
>> thought of:
>>
>> 1. When an arrow function is used as the second parameter to
>> `addEventListener`, the language can evaluate and use its scoped context
>> when the same function is used with a subsequent `removeEventListener call,
>> so essentially the following code would work when calling destroy.
>>
>>
>> ```js
>> class MyClass {
>> constructor () {
>> someNode.addEventListener('click', (e) => this.onClick(e))
>> }
>>
>> onClick (e) {
>> // do something here
>> }
>>
>> destroy () {
>> someNode.removeEventListener('click', (e) => this.onClick(e))
>> }
>> }
>>
>> ```
>>
>> 2. Another solution would be if we could maybe pass a method string as
>> the second parameter and then an optional context as the fourth parameter
>> to addEventListener and removeEventListener as follows:
>>
>>
>> ```js
>> class MyClass {
>> constructor () {
>> someNode.addEventListener('click', 'onClick', {}, this)
>> }
>>
>> onClick (e) {
>> // do something here
>> }
>>
>> destroy () {
>> someNode.removeEventListener('click', 'onClick', {}, this)
>> }
>> }
>>
>> ```
>>
>>
>> On Mon, May 9, 2016 at 9:52 AM Andrea Giammarchi <
>> andrea.giammarchi at gmail.com> wrote:
>>
>>> uhm, I've used commas ... anyway, the sugar is desugaring to old
>>> methods, this is working example:
>>>
>>> ```js
>>> class SomeClass {
>>>   constructor(someNode) {
>>>     someNode.addEventListener('click', this);
>>>   }
>>>   onclick(e) {
>>>     alert(this.constructor.name); // SomeClass
>>>   }
>>>   handleEvent(e) {
>>>     this['on' + e.type](e);
>>>   }
>>> }
>>>
>>> new SomeClass(document.documentElement);
>>> ```
>>>
>>> The difference with your example is that you will always be able to
>>> remove the instance without needing to store upfront every bound listener.
>>>
>>> To know where the `addEventListener` was set you always have the
>>> `e.currentTarget` so basically you have a weakmap between a node and an
>>> object where you can always retrieve the initial node that used the object
>>> through the event, keeping the node clean from "expando" links.
>>>
>>> More sugar than this, I'm not sure what would be.
>>>
>>> You could also have a simple naming convention where every method that
>>> starts with `on` will be set as listener using the current instance, and do
>>> the same, if necessary, on teardown/destroy.
>>>
>>> What kind of sugar would you use otherwise?
>>>
>>> The only proposal discussed so far is `el.addEventListener('click', ::
>>> this.onClick)`, unfortunately that doesn't solve anything because AFAIK
>>> they don't see any advantage in having `::this.onClick === ::
>>> this.onClick` which is what I've raised already as "that's what
>>> developers would expect" but apparently it's too costy or complicated or
>>> ... dunno.
>>>
>>> ¯\_(ツ)_/¯
>>>
>>> Best Regards
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>> On Mon, May 9, 2016 at 2:41 PM, Andrea Giammarchi <
>>> andrea.giammarchi at gmail.com> wrote:
>>>
>>>> Raising a concern about `addEventListener` and its context probably
>>>> made me think it was rather a WHATWG concern.
>>>>
>>>> Anyway, I don't know why you had to point out the `class` keyword ... I
>>>> mean ....
>>>>
>>>> ```js
>>>> class SomeClass {
>>>>   constructor(someNode) {
>>>>     someNode.addEventListener('click', this);
>>>>   },
>>>>   onclick(e) {
>>>>     alert(this.constructor.name); // SomeClass
>>>>   },
>>>>   handleEvent(e) {
>>>>     this['on' + e.type](e);
>>>>   }
>>>> }
>>>> ```
>>>>
>>>> There you go
>>>>
>>>> Best Regards
>>>>
>>>>
>>>> On Mon, May 9, 2016 at 2:38 PM, Mark Kennedy <mkay581 at gmail.com> wrote:
>>>>
>>>>> Wow that's so ironic because [I posted this same idea](
>>>>> https://github.com/whatwg/dom/issues/245#issuecomment-217816301)
>>>>> (literally
>>>>> copied and pasted) in WHATWG's "DOM land" and they told me this was an
>>>>> es-discuss issue. So which is it?
>>>>> Oh and thanks for the code sample but it uses the old prototypical
>>>>> method
>>>>> of replicating a class by creating a function which is now not the most
>>>>> efficient way (there's the `class` keyword). And I don't see how what
>>>>> you're doing isn't any different from a roundabout way of what I did. I
>>>>> already know about the `handleEvent()` stuff, I like that it's
>>>>> available
>>>>> and its polyfills. They are great, but my original question is to
>>>>> implement
>>>>> sugar so that you don't have to use polyfills or the `handleEvent()`.
>>>>> --
>>>>>
>>>>> mark
>>>>>
>>>>> Sent while riding a hoverboard...
>>>>> heyimmark.com :)
>>>>>
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>
>>>>>
>>>>
>>> --
>>
>> mark
>>
>> Sent while riding a hoverboard...
>> heyimmark.com :)
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160509/fd64d390/attachment-0001.html>


More information about the es-discuss mailing list