Hacker News new | ask | show | jobs
by genrez 1886 days ago
I am a noob to Javascript, so if someone knows better, than please correct me about this, but arrow functions aren't meant to replace normal function syntax, right? From [1], it seems like the main point of arrow syntax is to allow you to inherit the "this" parameter if you are inside a method. Meanwhile, you need normal function syntax if you are creating a constructor, making a method function for a prototype, or making generator functions. (I didn't even know javascript had generator functions until just now :))

So it seems a bit weird to me that they advocate using arrow function syntax instead of the regular syntax. They seem to be advocating using the new class syntax instead, so I guess they don't need the constructor or method creation features of the normal syntax, but I still don't see why they would specifically advocate for arrow function syntax. Is it faster? They say it interferes with other features, but which features?

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

2 comments

I've seen a majority of sources abandon the function keyword entirely in favor of const arrow declarations (and shorthand method syntax).

FWIW I personally like the function keyword, since it's clear what it is to non-JS readers, but primarily because it hoists to the top of its file, so unimportant utility functions can sit unobtrusively at the end of the file, thereby letting readers encounter more important logic earlier in the file.

Interesting to know that what the article recommends is indeed the industry standard. I'd forgotten about hoisting until you brought it up!
Not changing `this` is a huge benefit that shouldn't be ignored. Especially when you're programming in a more functional style, it makes sense to default to arrow functions because you never want to engage in `this` shenanigans anyway. So, yes, I'd say it's a pretty common idiom in the JS community to replace "normal" function declarations.
I agree that inheriting the `this` for arrow functions is beneficial. To me it seems like you would want to use the normal syntax for global functions for hoisting and to prevent unintentional re-definitions, the arrow functions where you would use lambda functions in other languages, and the class method syntax for methods.

side-note: Most of my JS experience is writing userscripts for myself, so I definitely do my share of 'this' shenanigans.

As a heads up since you mentioned "class method syntax", methods are one of the most important places to have lexical `this` binding in many scenarios.

Take the following example, which is a normal class method:

> alertSum() { alert(this.a + this.b); }

And here we have an arrow function used to create an instance method (just an arrow function assigned to a property on the instance):

> alertSum = () => { alert(this.a + this.b); }

Then let's say we want to pass the method directly as callback:

> this.button.addEventListener('click', this.alertSum)

The first example (class method syntax) won't have the necessary `this` context unless it has its context bound to the instance through `Function.prototype.bind`. There are other patterns to avoid this (e.g. wrapping all callbacks in arrow functions when passing them), but it's useful to consider that classes methods can easily create confusion because that's _exactly where_ someone more used to a different language may assume the `this` context is bound lexically.

Excellent point! I can see that getting confusing quickly.

Edit: I was confused about how this could work, so I dug through [1] for a bit. It appears that for each object of that class created, an arrow function will be created on that object and its this will indeed be bound to the same scope that the constructor function is bound to. This is really cleaver and I applaud whoever thought it up!

It is interesting to note that this creates a new arrow function on each object as opposed to the normal definitions which create a single function which is stored in the prototype of the class. (its easier to check this in a browser's dev console then it is to decode the spec)

This would suggest that one should use different approaches for different types of objects: It makes sense to use arrow functions for "resource" or "actor" objects, of which there are few but they may have callback functions. It makes sense to use normal method definitions for "plain old data", of which there may be many, (which would make the arrow functions too expensive) but they should not have callback functions.

[1] https://tc39.es/proposal-class-fields/unified.html

> This is really cleaver and I applaud whoever thought it up!

Not really. It's contortionist and wasteful and one of the many reasons why mainstream web apps are one big celebration of bloat on a boat.

The neophyte programmers who have turned into expert Modern JS programmers are always recommending arrow functions like this because they've never actually looked at the event listener interface. What happens is they try to make things more complicated than they need to be and bodge their event registration. So they apply a "fix" by doing what they do with everything else: layering on even more. "What we need," they say, "are arrow functions."

No.

Go the other way. Approach it more sensibly. You'll end up with a fix that is shorter than the answer that the cargo cult NPM/GitHub/Twitter programmers give. It's familiar to anyone coming from a world with interfaces as a language-level construct and therefore knows to go look at the interface definition of the interface that you're trying to implement.

Make your line for registering an event listener look like this: `this.button.addEventListener("click", this)`, and change the name of your `addSum` method to `handleEvent`. (Read it aloud. The object that we're dealing with (`this`) is something that we need to be able to respond to clicks, so we have it listen for them. Gee, what a concept.) In other words, the real fix is to make sure that the thing we're passing in to `addEventListener` is... actually an event listener.

This goes over 90% of frontend developers' heads (and even showing them this leads to them crying foul in some way; I've seen them try to BS their way through the embarrassment before) because most of the codebases they learned from were written by other people who, like themselves, only barely knew what they were doing. Get enough people taking this monkey-see-monkey-do approach, and from there you get "idioms" and "best practices" (no matter whether they were even "good" in the first place, let alone best).

I want to respond to this constructively, because you highlighted a useful interface I'd never taken note of previously; maybe we can focus this discussion on a technical level. Meanwhile, if you haven't had a positive response to technical feedback directed to JS engineers about this, I would encourage rereading the comment you wrote. I genuinely hope this is useful feedback, as I very much value insight from other languages and levels of abstraction, and I'd love for you not to get turned off to sharing that insight by a bad response from JS engineers. JavaScript is high level, and it's too easy to get disconnected from all of the work the engine and underlying computer are doing to make a program work in this environment.

---

As I understand the interface, practical use of the EventListener interface boils down to the implementer performing a form of event delegation, where you'd wind up delegating from a single `handleEvent()` method on a class to handle different event types and/or events with different target elements -- as opposed to a single click handler in a simple button click example. I'd love to understand and quantify the benefit of this. If it's a significant improvement, it'd be doubly unfortunate, as many callback-based interfaces in JavaScript APIs and libraries at a higher level don't support such a model.

Assuming there's a strong benefit, I also wonder if there's an opportunity for build-time tooling to rewrite code from ad-hoc callbacks to more efficient delegation along these lines. Tangentially, I also don't know about `Function.prototype.bind` vs wrapping arrow functions in terms of performance; that's also something I'm curious about, as they're often a 1:1 analog.

I'm tempted to test a few of these things out myself, but if you have references, that would be helpful.

> Make your line for registering an event listener look like this: `this.button.addEventListener("click", this)`, and change the name of your `addSum` method to `handleEvent`. (Read it aloud. The object that we're dealing with (`this`) is something that we need to be able to respond to clicks, so we have it listen for them. Gee, what a concept.) In other words, the real fix is to make sure that the thing we're passing in to `addEventListener` is... actually an event listener.

Eh, show me an object that both owns a button and is an event listener attached to that button, and I’ll bet you just showed me an object that violates the SRP. OO event listeners make sense to encapsulate state narrowly associated with handling the events being listened for.

But, having been doing OOP since I was a teenager in the late 1980s (and even that after programming for more than half a decade), I don’t prefer to do as much JS as possible in a functional style because I don’t get interfaces or other OO concepts, but because I understand the limits of their utility and managed not to be one of the one-paradigm-to-rule-them-all programmers that came out of the excessively OO-focussed pedagogy of much of the 90s and 00s.

I'm learning all sorts of things today! handleEvent certainly seems like the language certified way to do this. It does seem like that means you need a case statement in handleEvent if you want to listen for multiple events, which I guess is not the end of the world. It does solve the "redefine the function for every object" problem though, provided any third party apis understand the EventListener interface.