Why Use the function Keyword in JavaScript


Here are my reasons to use the function keyword when declaring functions in JS.

Using the function keyword in JavaScript has gone out of vogue since the advent of arrow functions (ie () => {}) in es2015. To declare a function with the function keyword, you write:

function myFunction() {}

And with an arrow function:

const myFunction = () => {}

In most cases, with exceptions, I prefer to use the former, the function keyword.

Code Order

When you use function, you can write your code in a readable order. By this, I mean the order that you would read a book. You don't have to search for where to start. You start, naturally, at the top and read down. You can write your entry point, top-level, most general code, at the top.

You can write other functions with which you've decomposed your problem beneath it. If you read from the top to the end of the entry-point function and understand as much as you need to without peeking into the abstractions, you can stop reading this module; you know enough. Layout example:

export function solveAProblem() {
  const stuff = helpSolveIt()
  const finalThing = anotherDetail(stuff)
  return finalThing
}

function helpSolveIt() {}

function anotherDetail() {}

This works because JavaScript hoists functions declared this way.

Fewer Symbols

function me() {} is easier to parse with my eyes than const me = () => {}. There are fewer symbols. Function keyword, name, args list, and body. As opposed to binding keyword, name, assignment operator, args list, arrow symbol and body.

function does seem long to type out for some reason, but somewhat incredibly typing function me() {} is 16 chars, while typing const me = () => {} is 19 chars. I care about this less than symbology.

For some reason, it also feels harder to type the arrow function. Probably because it's twice more to the number row on the keyboard to hit that equal sign with your pinky. And the arrow is a => combo-jump down to the bottom row.

It Says What it Is

Another point on readability: I like the function me() {} declaration because it says exactly what it is. From col 0, I know that this is a function. This label stands out.

When using const me = () => {}, I have to read to past the first equal size at least and then past the parens with potential args lists to pick out the arrow => to know for sure that this is a function. There's more to parse with my eyes when trying to understand the code.

An Actual Declaration

Using the function keyword is an actual function declaration. We can technically call it that. function me() {} is a function declared with the name me.

An arrow function () => {} is not a declaration, but a "function expression". The const me = is binding the expression to a name. Thus const me = () => {} is two operations: Defining an expression and then binding it. This is also why binding function expressions are not hoisted -- only declarations are hoisted.

Less Anonymous

One of the reasons that arrow functions were added to the language was for their terseness. This is especially true for anonymous functions. Using the function keyword, an anonymous function looks like:

function () {}

And an arrow function:

() => {}

And if the arrow function returns a single expression, the curlies and return keyword can be excluded, so it's very terse, and this can be nice!

But in general, I don't want to write code with anonymous functions. I want everything to be named. I want the stack traces to flow through well-named functions. It's more awkward to forget to name functions using the function keyword. It's very easy to forget to name an arrow function.

ES6 does infer the names of some functions based on name bindings, so that'll save you in stack traces sometimes. But there are caveats that I don't understand all the rules for, so I ensure naming with the declaration.

Function Expression Limitations

The first limitation is also a feature, depending on how you want to use it. In arrow functions, binding this happens at the site of definition only. Context can't be bound manually. In feature terms, you don't have to bind it manually. It inherits the context of its first object-bound parent. You can't use Function.bind. The first arg of Function.apply/call is also useless.

Arrow function expressions can't be used as constructors (can't call new on them and can't internally refer to super()).

Arrow functions don't have access to arguments. I rarely use it anyway, but sometimes I want to.

Finally, arrow functions can't be used as generator functions, those useful but mind-bending things.

Still Use Arrow Functions

Even given all the advantages of function keyword declarations, I still use arrow function expressions all the time. I even make them anonymous.

When I have a very short, single-use callback function, I'll often use an arrow function, as in an array mapping;

[1, 2, 3].map(n => n + 1)

But, again, it's worth noting that declarations and names can be useful here too (eg, [1, 2, 3].map(addOne)).

Cool Code

function is the OG function. Use it.

Or just write some cool code. Who cares.

Resources

Others' thoughts 'n docs.

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
  • https://plainenglish.io/blog/const-vs-function-for-react-functional-components
  • https://dev.to/skinnypetethegiraffe/function-or-const-which-do-you-prefer-typescriptjavascript-apa
  • https://softwareengineering.stackexchange.com/questions/364086/why-use-const-foo-instead-of-function-foo
  • https://mayanovarini.medium.com/functions-in-javascript-declaration-expression-arrow-d6f907dc850a
  • https://hackernoon.com/three-reasons-i-avoid-anonymous-js-functions-like-the-plague-7f985c27a006
  • https://github.com/johnstonbl01/eslint-no-inferred-method-name