I offer the readers of "Habrakhabr" a free translation of the article "Constant confusion: why I still use JavaScript function statements" by Bill Suro (Bill Sourour).In the distant 90s, when I first studied JavaScript, we tried to write “Hello World” using the
function
operator. Like that:
function helloWorld() { return 'Hello World!'; }
At the present time, cool guys write the “Hello World” function like this:
')
const helloWorld = () => 'Hello World!';
The arrow function added in JavaScript in the ES2015 standard is used here. She looks damn fine. Everything fits in one line. So briefly. So great.
It is not surprising that the switch functions have become
one of the most popular features of the new standard.
When I first saw them, I looked something like Fry from Futurama.
Even considering that Babel is freeSo, after 20 years of studying JavaScript and after starting using ES2015 in some projects, how do you think, how will I write Hello World today? Like this:
function helloWord() { return 'Hello World!'; }
After I showed you a new way, you can hardly even look at the “old school” code presented above.
Whole 3 lines on such a small simple function? Why are there so many extra characters?
I know what you're thinking:
Is not nobody got time for that!Do not assume that I do not like the switch functions. But in the case when I need to declare a top-level function in my code, I will use the old-fashioned
function operator.
I hope from this quotation by
Martin “Uncle Bob” it will become clear why I am doing this.
The ratio of time spent reading the code in relation to the time spent writing it is 10 to 1. We constantly read our old code while working on new code.
Due to the fact that this ratio is too high, we want to read our code in the future was easy and relaxed, despite the fact that this will have to spend more effort while writing it.
Robert Martin: Clean Code: creating, analyzing and refactoring
The
function
operator has 2 distinct advantages over the arrow functions.
Advantage # 1: Clarity of intent.
When we look at thousands of lines of code a day, it will be helpful to understand the programmer’s intentions as quickly and easily as possible.
Take a look at this:
const maxNumberOfItemsInCart = ...;
You have already viewed almost the entire line, but you still do not understand what will be in place of the ellipsis: a function or some value. It can be like:
const maxNumberOfItemsInCart = 100;
... and:
const maxNumberOfItemsInCart = (statusPoints) => statusPoints * 10;
If you use the
function operator, then there will be no ambiguity.
Take a look at:
const maxNumberOfItemsInCart = 100;
… vs:
function maxNumberOfItemsInCart(statusPoints) { return statusPoints * 10; }
The intentions of the programmer are clear from the very beginning of the line.
Perhaps you are using a code editor with syntax highlighting. Maybe you read quickly. Rather, you just think that you will not get much benefit from adding a pair of lines.
I understand you. The short record still looks pretty good.
In fact, if this were my only reason, then I would be able to convince myself that readability can be sacrificed.
But still this is not my only reason.
Advantage # 2: Announcement order == Execution order
In the ideal case, I would like to declare my code approximately in the order in which I want it to be executed.
But when using the switch functions, this will be an obstacle for me: any value declared through
const
is
not available until the execution of the code reaches it.
Get ready for a bunch of gibberish to prove (hopefully) that I understand what I'm talking about.The only thing you need to understand in what is written below is that you cannot use
const before you declare it.
This code will cause an error:
sayHelloTo('Bill'); const sayHelloTo = (name) => `Hello ${name}`;
All because at the moment when the JavaScript engine considers the code, it will bind to the “sayHelloTo” function, but does not initialize it.
All declarations in JavaScript are attached at an early stage, but they are initialized at different times. In other words, JavaScript binds the declaration of the “sayHelloTo” constant — reads it, moves it up and allocates space in memory for its storage — but does not set it to any value until it reaches it during execution.
The time between the “sayHelloTo” binding and its initialization is called the “temporal dead zone” (TDZ).
If you are using ES2015 directly in the browser, without translating the code in ES5 using Babel, the example below will also generate an error:
if(thing) { console.log(thing); } const thing = 'awesome thing';
If we replace
const with
var here , we will not get an error. The point is that the variables are initialized with the value
undefined immediately during the binding, in contrast to constants. But something I digress ...
The
function statement, unlike
const , does not suffer from the TDZ problem. This code will be valid:
sayHelloTo('Bill'); function sayHelloTo(name) { return `Hello ${name}`; }
This is because the
function statement allows you to initialize a function immediately after binding - before any code is executed.
Thus, it becomes unimportant where the function code is located; it becomes available from the very beginning of execution.
Using
const makes us write code that looks random. We must first start working with low-level functions and then go higher and higher. My brain is not working in this direction. I want to see the big picture before I get into the details.
Most of the code is written by people. So it is worth considering that the understanding of most people is directly related to the order of implementation.
In fact, wouldn't it be great if we provide a brief summary of a small part of our API at the beginning of our code? With the
function operator, we can easily do this.
Look at this (partly made-up) module for a basket of goods ...
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 the arrow functions, it will look something like this:
const _isValidCustomer = (customerId) => ... const _isValidCart = (cart) => ... const _isValidItem = (item) => ... const createCart = (customerId) => ... const addItemToCart = (item, cart) => ... ... export { createCart, addItemToCart, removeItemFromCart, cartSubTotal, cartTotal, saveCart, clearCart, }
Now imagine that this is a large module with a huge number of small internal functions. What would you prefer in this case?
Surely there are people who will argue that using something before its announcement is unnatural and can lead to unknown consequences. Those who assert this may be first-class experts and understand their field, but whatever one may say - this is just your opinion, and not a confirmed fact.
I believe that the code is a means of communication. Really good code can tell a first-class story. I allow compilers, transpilers and minimizers to optimize the code for the machine. But I myself want to optimize my code for a simple person who just wants to understand what I have written.
So what about switch functions?
Yes. They are still beautiful.
I usually use the arrow functions to pass a small function as a value for a function one level higher. I use them with
promise , with
map , with
filter , with
reduce . This is where they will be the perfect choice.
Here are some examples:
const goodSingers = singers.filter((singer) => singer.name !== 'Justin Bieber'); function tonyMontana() { return getTheMoney().then((money) => power) .then((power) => women); }
On this I probably will finish. Thank you for reading!