back

by masswerk·2y ago·view on hn ↗
Regarding components, states and encapsulation, also mind the much ignored `Object.prototype.handleEvent`, which has been around since 1997. I, know, it's OO, but it is actually simple:

  function Counter(buttonElement) {
      this.clicks = 0;
      this.handleEvent = function(event) {
         switch (event.type) {
            case 'click':
               this.clicks++;
               console.log("I was clicked " + this.clicks + " times." );
               break;
            //handle any other event types
         }
      };
      buttonElement.addEventListener("click", this, false);
   }

   const myCounter = new Counter( someButtonElement );
(In case you wouldn't know: if an object is used as an event listener, its `handleEvent()` method will be called with that object bound to `this`. In a way, arrow functions are working around what had been already solved by this mechanism.)
2 comments
I'm not entirely sure what this adds in this case, though. For example, in the code you've written, as far as I can tell there's no advantage compared to defining the function as a closure variable and using it directly, rather than defining it as an attribute on `this` and passing the `this` object around.

In addition, with the `handleEvent` approach, you need to handle all events within a single function. But it's fairly easy to create multiple functions within a single function scope and pass them to different event handlers, thus avoiding the need for the large (and potentially error-prone) switch statement if you end up needing to handle lots of events.

Have you found cases where `handleEvent` works better than just defining local variables within a function and just using those? It seems to me that you wouldn't even need arrow functions to take advantage of the natural power of closures in this context.

One of the advantages is that this can be defined and handled on the prototype level and not just in the instance. Personally, I'd see the interrupt-like catch-all behavior rather as an advantage (all messages are received in a single slot), but this may be up to personal taste. And, while it's true that similar binding can be achieved using closures, this has also been true for arrow functions. It's just a convenient solution in the mindset of the original ECMA Script (ECMA 262-2).

Regarding prototypes, mind how this shares methods between instances, rather than consuming (and locking) resources by individual closures created in each of the instances (which, when GC was still based on reference count, would also have meant memory leaks):

  function Counter(buttonElement) {
     this.clicked = 0;
     buttonElement.addEventlistener("click", this, false);
  }
  Counter.prototype = {
     reset: function() { this.clicked = 0; },
     log: function() { concole.log("I was clicked " + this.clicked + " times."); },
     handleEvent: function(event) { this.clicked++; this.log(); }
  };
Closures really are poor man's objects.
And vice versa!
Sadly React disallows this pattern even though this interface is in the DOM spec
In the context of coroutines, the nice thing about this is that – as a catch-all trap – it works much like an interrupt that is internal to that specific object. (Of course, only when the GUI thread allows for event processing.) On the one hand, this is pretty much the essence of what events should be about: invoking a dormant object (or context) by the means of a message. On the other hand, this inherent locality may pose a problem for any framework. (And – in the age of AI generated images – on the third hand, this is why I personally don't like frameworks that much.)
It doesn’t disallow it, it’s just outside of how the framework operates. If you really want to make the two talk, you could, with the cost of having to maintain it.