Чому я все ще використовую function в JavaScript?

Пропоную читачам «Хабрахабра» вільний переклад статті «Constant confusion: why I still use JavaScript function statements» від Білла Суро (Bill Sourour).

У далеких 90-х, коли я тільки вивчав JavaScript, ми намагалися написати «Hello World» з допомогою оператора
function
. Приблизно так:

function helloWorld() {
return 'Hello World!';
}

У теперішній же час круті хлопці пишуть функцію «Hello World» ось так:

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

Тут використовується стрілкова функція, додана в JavaScript у стандарті ES2015. Вона виглядає дуже чудово. Все вміщається в один рядок. Так коротко. Так чудово.

Не дивно, що стрілочні функції стали <a href=«www.2ality.com/2015/07/favorite-es6-features.html»однією з найпопулярніших особливостей нового стандарту.

Коли я вперше їх побачив, я виглядав приблизно як Фрай з Футурами

image
Навіть якщо врахувати, що Babel безкоштовний

Так ось, через 20 років вивчення JavaScript і після початку використання ES2015 в деяких проектах, як думаєте, яким чином я напишу Hello World сьогодні? Ось так:

function helloWord() {
return 'Hello World!';
}

Після того, як я показав вам новий спосіб, ви навряд чи станете навіть дивитися на код «старої школи», представлений вище.

Цілих 3 рядки на таку маленьку просту функцію?! Навіщо тут стільки зайвих символів?

Я знаю про що ви думаєте:

image
Ні у кого немає часу на це!

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

Сподіваюся з цієї цитати Мартіна «Дядька Боба» стане зрозуміло, навіщо я це роблю.

Відношення часу, витраченого на читання коду, по відношенню до часу, витраченого на його написання, складає 10 до 1. Ми постійно читаємо наш старий код під час роботи над новим кодом.

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

Роберт Мартін: Чистий код: створення, аналіз і рефакторинг
Оператор
function
має 2 явні переваги в порівнянні зі стрілочними функціями.

Перевага №1: Ясність наміри
Коли ми переглядаємо тисячі рядків коду в день, буде корисним розуміти наміри програміста так швидко і легко, як це тільки можливо.

Гляньте на це:

const maxNumberOfItemsInCart = ...;

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

const maxNumberOfItemsInCart = 100;

… так:

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

Якщо ж ви використовуєте оператор function, то ніякої двозначності вже не буде.

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

const maxNumberOfItemsInCart = 100;

… проти:

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

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

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

Я вас розумію. Коротка запис все ще виглядає досить привабливо.

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

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

Перевага №2: Порядок оголошення == Порядок виконання
В ідеальному випадку, я хотів би оголошувати мій код приблизно в тому порядку, в якому я хочу, щоб він виконувався.

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

image
Приготуйтеся до купи тарабарщини, яка повинна довести (сподіваюся), що я розбираюся в тому, про що кажу

Єдина річ, яку ви повинні усвідомити в тому, що написано нижче, це те, що ви не можете використовувати const до того, як ви оголосите її.

Даний код викличе помилку:

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

Все тому що в той момент, коли движок JavaScript вважає код, то він зробить прив'язку для функції «sayHelloTo», але не проинициализирует її.

Всі оголошення в JavaScript прив'язуються на ранній стадії, але вони ініціалізуються в різний час. Іншими словами, JavaScript прив'язує оголошення константи «sayHelloTo» зчитує її, переміщує вгору і виділяє місце в пам'яті під її зберігання — але при цьому не встановлює їй якогось значення до того моменту, поки не дійде до неї в процесі виконання.

Час між прив'язкою «sayHelloTo» та її ініціалізацією називається «тимчасова мертва зона» (temporal dead zone — TDZ).

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

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

Якщо ж замінити тут const var, то ми не отримаємо помилок. Справа в тому, що змінні ініціалізуються зі значенням undefined відразу під час прив'язки, на відміну від констант. Але щось я відволікся…

Оператор function на відміну від const, не страждає від проблеми TDZ. Даний код буде валідним:

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

Це тому, що оператор function дозволяє ініціалізувати функцію відразу ж після прив'язки — до того, як буде виконаний який-небудь код.

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

Використання const змушує нас писати код, який виглядає безладно. Ми повинні спочатку починати працювати з низькорівневими функціями і потім підніматися все вище. Мій мозок не працює в цьому напрямку. Я хочу бачити загальну картину до того, як буду заглиблюватися в деталі.

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

За фактом, хіба не буде чудово, якщо ми надамо коротку зведення невеликої частини нашого API на початку нашого коду? З оператором function ми легко можемо це зробити.

Подивіться на цей (частково вигаданий) модуль для кошика товарів…

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

Зі стрілочними функціями він буде виглядати якось так:

const _isValidCustomer = (customerId) => ...
const _isValidCart = (cart) => ...
const _isValidItem = (item) => ...
const createCart = (customerId) => ...
const addItemToCart = (item, cart) => ...
...

export {
createCart,
addItemToCart,
removeItemFromCart,
cartSubTotal,
cartTotal,
saveCart,
clearCart,
}

А тепер уявіть, що це великий модуль з величезною кількістю невеликих внутрішніх функцій. Що ви віддасте перевагу в такому випадку?

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

Я ж вважаю, що код є засобом спілкування. Дійсно хороший код може розповісти першокласну історію. Я дозволяю компиляторам, транспайлерам і мінімізаторам оптимізувати код для машини. Але сам я хочу оптимізувати мій код для простої людини, яка просто хоче зрозуміти що ж у мене написано.

Так що ж щодо стрілочний функцій?
Так. Вони прекрасні.

Я зазвичай використовую стрілочні функції для передачі невеликий функції значення функції рівнем вище. Я використовую їх promise, map, filter, reduce. Саме тут вони будуть прекрасним вибором.

Ось деякі приклади:

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

На цьому я, мабуть, і закінчу. Спасибі за читання!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.