Confusión constante: por qué sigo usando declaraciones de funciones de JavaScript

A finales de los 90, cuando aprendí JavaScript, nos enseñaron a escribir la función "Hola mundo" usando una declaración de función . Me gusta esto…

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

En estos días, parece que todos los niños geniales están escribiendo la función "Hola mundo" de esta manera ...

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

Esta es una expresión de función en JavaScript ES2015 y es increíblemente sexy. Es hermoso a la vista. Todo es una sola línea. Tan escueto. Adorable.

Utiliza una función de flecha que es una de las características más populares de ES2015.

Cuando vi esto por primera vez pensé:

Entonces, después de casi 20 años de JavaScript y después de usar ES2015 en varios proyectos, así es como escribiría la función "Hola mundo" hoy:

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

Ahora que les he mostrado la nueva forma, estoy seguro de que apenas pueden soportar mirar el código de la vieja escuela arriba.

¡Tres líneas completas para una simple función! ¡Todos esos personajes extra!

Sé lo que estás pensando…

Me encantan las funciones de flecha, de verdad. Pero cuando necesito declarar una función de nivel superior en mi código, sigo usando una buena declaración de función pasada de moda.

Esta cita del "tío Bob" Martin explica por qué:

“… La proporción de tiempo dedicado a leer versus escribir es más de 10 a 1. Estamos constantemente leyendo código antiguo como parte del esfuerzo por escribir código nuevo.

Debido a que esta proporción es tan alta, queremos que la lectura del código sea fácil incluso si dificulta la escritura ".

- Robert C. Martin

Código limpio: un manual de artesanía de software ágil

Las declaraciones de función tienen dos claras ventajas sobre las expresiones de función:

Ventaja n. ° 1: claridad de intención

Al escanear miles de líneas de código al día, es útil poder averiguar la intención del programador de la manera más rápida y sencilla posible.

Mira esto:

const maxNumberOfItemsInCart = ...;

Lees todos esos caracteres y todavía no sabes si la elipsis representa una función o algún otro valor. Podría ser:

const maxNumberOfItemsInCart = 100;

... o fácilmente podría ser:

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

Si usa una declaración de función, no existe tal ambigüedad.

Mirar:

const maxNumberOfItemsInCart = 100;

…versus:

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

La intención es clara desde el principio de la línea.

Pero tal vez esté usando un editor de código que tiene algunas pistas de codificación de colores. Quizás eres un lector veloz. Tal vez simplemente no crea que sea tan importante.

Te escucho. La concisión todavía se ve bastante sexy.

De hecho, si esta fuera mi única razón, podría haber encontrado una manera de convencerme de que es una compensación que vale la pena.

Pero es no la única razón ...

Ventaja # 2: Orden de declaración == orden de ejecución

Idealmente, quiero declarar mi código más o menos en el orden en que espero que se ejecute.

Este es el éxito para mí: cualquier valor declarado usando la palabra clave const es inaccesible hasta que la ejecución lo alcance.

Advertencia justa: estoy a punto de hacer todo, "Profesor JavaScript" en usted. Lo único que debe comprender en toda la jerga siguiente es que no puede usar una constante hasta que la haya declarado .

El siguiente código arrojará un error:

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

Esto se debe a que, cuando el motor de JavaScript lee el código, se unen “sayHelloTo”, pero no va a inicializar la misma.

Todas las declaraciones en JavaScript están vinculadas antes, pero se inicializan de manera diferente.

En otras palabras, JavaScript enlaza la declaración de "sayHelloTo" - la lee primero y crea un espacio en la memoria para mantener su valor - pero no establece "sayHelloTo" en nada hasta que lo alcanza durante la ejecución .

El tiempo entre el enlace de "sayHelloTo" y la inicialización de "sayHelloTo" se denomina zona muerta temporal (TDZ).

Si está utilizando ES2015 directamente en el navegador (en lugar de transpilar a ES5 con algo como Babel), el siguiente código también arroja un error:

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!