Постоянно объркване: защо все още използвам инструкции за функции на 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!