Ständige Verwirrung: Warum ich immer noch JavaScript-Funktionsanweisungen verwende

In den späten 90ern - als ich JavaScript lernte - wurde uns beigebracht, die Funktion „Hello World“ mithilfe einer Funktionsanweisung zu schreiben . So was…

function helloWorld() { return ‘Hello World!’; }

Heutzutage scheinen alle coolen Kids die Funktion „Hallo Welt“ so zu schreiben…

const helloWorld = () => 'Hello World!';

Dies ist ein Funktionsausdruck in ES2015 JavaScript und es ist höllisch sexy. Es ist schön anzusehen. Es ist alles eine Zeile. So knapp. So lieblich.

Es verwendet eine Pfeilfunktion, die eine der beliebtesten Funktionen von ES2015 ist.

Als ich das zum ersten Mal sah, war ich wie folgt:

Nach fast 20 Jahren JavaScript und nach der Verwendung von ES2015 in einer Reihe von Projekten würde ich heute die Funktion „Hallo Welt“ folgendermaßen schreiben:

function helloWorld() { return ‘Hello World!’; }

Jetzt, da ich Ihnen den neuen Weg gezeigt habe, können Sie es sicher kaum ertragen, sich den alten Schulcode oben anzusehen.

Drei ganze Zeilen für eine einfache kleine Funktion! All diese zusätzlichen Charaktere!

Ich weiß was du denkst…

Ich liebe Pfeilfunktionen, das tue ich wirklich. Wenn ich jedoch eine Funktion der obersten Ebene in meinem Code deklarieren muss, verwende ich immer noch eine gute altmodische Funktionsanweisung.

Dieses Zitat von "Onkel Bob" Martin erklärt warum:

„… Das Verhältnis von Lese- und Schreibzeit liegt weit über 10 zu 1. Wir lesen ständig alten Code, um neuen Code zu schreiben.

Weil dieses Verhältnis so hoch ist, möchten wir, dass das Lesen von Code einfach ist, auch wenn es das Schreiben erschwert. “

- Robert C. Martin

Clean Code: Ein Handbuch für agile Software-Handwerkskunst

Funktionsanweisungen haben zwei klare Vorteile gegenüber Funktionsausdrücken:

Vorteil Nr. 1: Klarheit der Absicht

Wenn Sie täglich Tausende von Codezeilen durchsuchen, ist es hilfreich, die Absicht des Programmierers so schnell und einfach wie möglich herauszufinden.

Schau dir das an:

const maxNumberOfItemsInCart = ...;

Sie lesen alle diese Zeichen und wissen immer noch nicht, ob die Auslassungspunkte eine Funktion oder einen anderen Wert darstellen. Es könnte sein:

const maxNumberOfItemsInCart = 100;

… Oder es könnte genauso gut sein:

const maxNumberOfItemsInCart = (statusPoints) => statusPoints * 10;

Wenn Sie eine Funktionsanweisung verwenden, gibt es keine solche Mehrdeutigkeit.

Ansehen:

const maxNumberOfItemsInCart = 100;

…gegen:

function maxNumberOfItemsInCart(statusPoints) { return statusPoints * 10; }

Die Absicht ist vom Anfang der Linie an kristallklar.

Aber vielleicht verwenden Sie einen Code-Editor, der einige Hinweise zur Farbcodierung enthält. Vielleicht bist du ein Speedreader. Vielleicht denkst du einfach nicht, dass es eine große Sache ist.

Ich höre dich. Die Knappheit sieht immer noch ziemlich sexy aus.

Wenn dies mein einziger Grund wäre, hätte ich vielleicht einen Weg gefunden, mich davon zu überzeugen, dass es ein lohnender Kompromiss ist.

Aber es ist nicht mein einziger Grund ...

Vorteil Nr. 2: Reihenfolge der Erklärung == Reihenfolge der Ausführung

Im Idealfall möchte ich meinen Code mehr oder weniger in der Reihenfolge deklarieren, in der er voraussichtlich ausgeführt wird.

Dies ist der Showstopper für mich: Auf jeden Wert, der mit dem Schlüsselwort const deklariert wurde, kann nicht zugegriffen werden, bis die Ausführung ihn erreicht.

Faire Warnung: Ich werde alles tun, "Professor JavaScript" auf Sie. Das einzige, was Sie im folgenden Jargon verstehen müssen, ist, dass Sie eine Konstante erst verwenden können, wenn Sie sie deklariert haben .

Der folgende Code gibt einen Fehler aus:

sayHelloTo(‘Bill’); const sayHelloTo = (name) => `Hello ${name}`;

Dies liegt daran, dass die JavaScript-Engine beim Lesen des Codes "sayHelloTo" bindet , ihn jedoch nicht initialisiert .

Alle Deklarationen in JavaScript sind frühzeitig gebunden, werden jedoch unterschiedlich initialisiert.

In other words, JavaScript binds the declaration of “sayHelloTo” — reads it first and creates a space in memory to hold its value — but it doesn’t set “sayHelloTo” to anything until it reaches it during execution.

The time between “sayHelloTo” being bound and “sayHelloTo” being initialized is called the temporal dead zone (TDZ).

If you’re using ES2015 directly in the browser (as opposed to transpiling down to ES5 with something like Babel), the following code actually throws an error too:

if(thing) { console.log(thing); } const thing = 'awesome thing';

The code above, written using “var” instead of “const”, would not throw an error because vars get initialized as undefined when they are bound, whereas consts are not initialized at all at bind time. But I digress…

Function statements do not suffer from this TDZ problem. The following is perfectly valid:

sayHelloTo(‘Bill’); function sayHelloTo(name) { return `Hello ${name}`; }

This is because function statements get initialized as soon as they are bound — before any code is executed.

So, no matter when you declare the function, it will be available to its lexical scope as soon as the code starts executing.

What I’ve just described above forces us to write code that looks upside down. We have to start with the lowest level function and work our way up.

My brain doesn’t work that way. I want the context before the details.

Most code is written by humans. So it makes sense that most people’s order of understanding roughly follows most code’s order of execution.

In fact, wouldn’t it be nice if we could provide a little summary of our API at the top of our code? With function statements, we totally can.

Check out this (somewhat contrived) shopping cart module…

export { createCart, addItemToCart, removeItemFromCart, cartSubTotal, cartTotal, saveCart, clearCart, } function createCart(customerId) {...} function isValidCustomer(customerId) {...} function addItemToCart(item, cart) {...} function isValidCart(cart) {...} function isValidItem(item) {...} ...

With function expressions it would look something like…

... const _isValidCustomer = (customerId) => ... const _isValidCart = (cart) => ... const _isValidItem = (item) => ... const createCart = (customerId) => ... const addItemToCart = (item, cart) => ... ... export { createCart, addItemToCart, removeItemFromCart, cartSubTotal, cartTotal, saveCart, clearCart, }

Imagine this as a larger module with many small internal functions. Which would you prefer?

There are those who will argue that using something before you’ve declared it is unnatural, and can have unintended consequences. There are even extremely smart people who have said such things.

It is definitely an opinion — not a fact — that one way is better than the other.

But if you ask me: Code is communication. Good code tells a story.

I’ll let the compilers and the transpilers, the minifiers and the uglyfiers, deal with optimizing code for the machines.

I want to optimize my code for human understanding.

What about those arrow functions, though?

Yes. Still sexy and still awesome.

I typically use arrow functions to pass a small function as a value to a higher order function. I use arrow functions with promises, with map, with filter, with reduce. They are the bees knees, my friends!

Some examples:

const goodSingers = singers.filter((singer) => singer.name !== 'Justin Bieber'); function tonyMontana() { return getTheMoney() .then((money) => money.getThePower()) .then((power) => power.getTheWomen()); }

I used a few other new JavaScript features in this article. If you want to learn more about the latest JavaScript standard (ES2015) and all the cool features it has to offer, you should get my quick start guide for free.

My goal is always to help as many developers as possible, if you found this article useful, please hit the ❤ (recommend) button so that others will see it. Thanks!