Постійна плутанина: чому я все ще використовую оператори функції JavaScript

Ще наприкінці 90-х - коли я вивчив JavaScript - нас навчили писати функцію «Hello World» за допомогою оператора функції . Подобається це…

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

У наші дні здається, що всі круті діти пишуть функцію «Hello World» так ...

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

Це вираз функції в ES2015 JavaScript, і це, напевно, сексуально. На це красиво дивитись. Це все одна лінія. Так лаконічно. Так мило.

Він використовує функцію стрілки, яка є однією з найпопулярніших функцій ES2015.

Коли я вперше побачив це, я був таким:

Отже, після майже 20 років JavaScript та після використання ES2015 у ряді проектів, ось як я написав би сьогодні функцію «Hello World»:

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

Тепер, коли я показав вам новий шлях, я впевнений, що ви ледве витримуєте погляд на старий шкільний код вище.

Три цілі рядки для простої маленької функції! Усі ці зайві символи!

Я знаю, про що ти думаєш ...

Я люблю функції стрілок, я справді їх люблю. Але коли мені потрібно оголосити функцію верхнього рівня у своєму коді, я все одно використовую старий добрий вираз функції.

Ця цитата "Дядька Боба" Мартіна пояснює, чому:

«… Співвідношення часу, витраченого на читання, до написання значно перевищує 10 до 1. Ми постійно читаємо старий код як частину зусиль для написання нового коду.

Оскільки цей коефіцієнт настільки високий, ми хочемо, щоб читання коду було легким, навіть якщо це ускладнює написання ".

- Роберт С. Мартін

Чистий код: Довідник з майстерності майстерності програмного забезпечення

Оператори функцій мають дві очевидні переваги перед виразами функцій:

Перевага №1: Ясність намірів

Під час сканування тисяч рядків коду на день корисно мати можливість якомога швидше і простіше з’ясувати намір програміста.

Погляньте на це:

const maxNumberOfItemsInCart = ...;

Ви читаєте всі ці символи і досі не знаєте, чи багатоточість представляє функцію чи якесь інше значення. Це може бути:

const maxNumberOfItemsInCart = 100;

... або це може бути так само легко:

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

Якщо ви використовуєте оператор функції, такої двозначності немає.

Подивись на:

const maxNumberOfItemsInCart = 100;

…проти:

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

Намір кристально зрозумілий з самого початку рядка.

Але, можливо, ви використовуєте редактор коду, який має деякі підказки щодо кольорового кодування. Можливо, ти читач швидкості. Можливо, ви просто не думаєте, що це така велика справа.

Я чую тебе. Лаконічність все ще виглядає досить сексуально.

Насправді, якби це була моя єдина причина, я міг би знайти спосіб переконати себе, що це вартий компроміс.

Але це не єдина моя причина ...

Перевага №2: Порядок декларування == Порядок виконання

В ідеалі я хочу оголосити свій код більш-менш у тому порядку, в якому я очікую, що він буде виконаний.

Це для мене showstopper: будь-яке значення, оголошене за допомогою ключового слова const, недоступне, доки виконання не дійде до нього.

Справедливе попередження: Я збираюся піти на все, "професор JavaScript" на вас. Єдине, що ви повинні розуміти у всьому наведеному нижче жаргоні, - це те, що ви не можете використовувати const, поки не оголосите його .

Наступний код видасть помилку:

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

Це тому, що коли механізм JavaScript читає код, він буде прив’язувати “sayHelloTo”, але не ініціалізуватиме його.

Всі оголошення в JavaScript прив'язані достроково, але вони ініціюються по-різному.

Іншими словами, JavaScript прив'язує декларацію “sayHelloTo” - спочатку зчитує її та створює простір у пам’яті для зберігання її значення - але він не встановлює “sayHelloTo” ні до чого, доки він не досягне його під час виконання .

Час між прив'язуванням "sayHelloTo" та ініціалізацією "sayHelloTo" називається тимчасовою мертвою зоною (TDZ).

Якщо ви використовуєте ES2015 безпосередньо у браузері (на відміну від транслірування до ES5 чимось на зразок Babel), наступний код насправді також видає помилку:

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!