Почему я все ещё использую function в JavaScript?

в 14:57, , рубрики: arrow functions, es2015, 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. Она выглядит чертовски прекрасно. Всё умещается в одну строку. Так кратко. Так замечательно.

Неудивительно, что стрелочные функции стали одной из самых популярных особенностей нового стандарта.

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

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);
}

На этом я пожалуй и закончу. Спасибо за чтение!

Автор: e_juke

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js