63

Edit added 2+ years later

I "checked" the @dandavis answer because it answers my original question, giving reasons to prefer const foo. However, I am completely convinced by the @Wayne Bloss answer that function foo() is generally superior.

Original Question here

For example, in this Redux video, the instructor always uses syntax like

const counter = (state=0, action) => {
   ... function body here
}

where I would just use the "traditional"

function counter(state=0, action) {
   ... function body here
}

Which is actually shorter and, IMO, clearer. It's easier to scan the fairly even and structured left edge of the page for the word "function" than scan the raggedy right edge for a small "=>".

Other than this, and trying to be objective, not opinion, is there some useful difference or advantage to the newfangled syntax?

user949300
  • 8,679
  • 2
  • 26
  • 35
  • 3
    This question on StackOverflow may interest you: https://stackoverflow.com/questions/34361379/arrow-function-vs-function-declaration-expressions-are-they-equivalent-exch – Vincent Savard Jan 16 '18 at 18:53
  • 4
    I'm no JavaScript expert, but I'm guessing `const` helps ensure the function doesn't get redefined later on. – MetaFight Jan 16 '18 at 19:13
  • Thanks @VincentSavard, that's perfect, and basically what I expected: Other than "this", and prototype/class stuff, there appears to be no real difference. – user949300 Jan 16 '18 at 19:44
  • 3
    @user949300 There _is_ a difference, the one MetaFight mentions. Also, protype / "this stuff" quickly become critical distinctions, as well. – msanford Jan 16 '18 at 20:00
  • 1
    Long story short: You should value clear and concise over an edge-case benefit. – Wayne Bloss Jul 10 '19 at 15:04

2 Answers2

51

Function statements (named functions, 2nd syntax shown) are hoisted to the top of the full lexical scope, even those behind arbitrary and control blocks, like if statements. Using const (like let) to declare a variable gives it block scope, stops the full hoisting (hoisting to mere block), and ensures it cannot be re-declared.

When concatenating scripts together, or some using other package-building tools, function hoisting can break conflicting scripts in ways that are difficult to debug as it fails silently. A re-declared const will throw an exception before the program can run, so it's much easier to debug.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
dandavis
  • 640
  • 6
  • 4
  • 1
    Thanks. good answer. I've mainly worked on smaller JS projects, or node.js server projects where they have a good module system for namespacing. But just starting on a more client-side project using bundlers and this is good insight. – user949300 Jan 17 '18 at 17:55
  • 5
    Just a note that [eslint no-func-assign](https://eslint.org/docs/rules/no-func-assign) may catch this redeclaration issue. – user949300 Jan 26 '18 at 01:18
  • 16
    Writing code that has confusing signals in order to get the benefits of a statically typed language is a reason to use Typescript, not `const`. It's a bit short-sighted, IMO, to start using `const` everywhere for this reason in the age of eslint, webpack, babel and so forth. Nobody is concatenating files together manually anymore for at least a decade now. – Wayne Bloss Jul 10 '19 at 14:54
  • so, what should I use at the end ? – aspirinemaga Jan 26 '22 at 12:43
  • Can you provide an example of problematic hoisting? The following seems to contradict your statement: `foo(); if (false) function foo() {}` results in "foo is not a function" – Rich Remer May 17 '23 at 15:49
  • @RichRemer: `alert( 'foo' in window); if (false) function foo() {} ` shows true in the alert, even if the value is `undefined`, which can be quite confusing... – dandavis May 17 '23 at 16:34
44

Here are some reasons you might want to use function:

  1. The signaling is clear and concise. This is far more beneficial than any of the edge-case hoisting concerns that are listed in the other answer. Nobody is using package-building tools that concatenate files together anymore for at least a decade now. We use Babel, webpack, rollup, etc. and they won't break if you use function inside of a module.

  2. You actually want hoisting within modules because as you can see from the code below, the const declaration of tryDoTheThing fail silently and won't be caught until you try to call it.

  3. A named function will show up in your stack trace with it's name so it's easier to debug in the most common case where you have an error in your function.

To get a deeper understanding of the differences, read more here.

Example of problems with block scoping:

via https://gist.github.com/stephenjfox/fec4c72c7f6ae254f31407295dc72074


/*
This shows that, because of block-scoping, const function references must be
invoked in-order or else things will fail silently.
const's are added the name space serially (in the order in which they appear)
and much of the body isn't declared when we first try to invoke or functions
*/


const tryDoTheThing = () => {
  console.log(`This is me trying to be awesome ${getAwesome()}`)
}


// "getAwesome is not defined", because it is referenced too early
tryDoTheThing() // comment to see the rest work


const getAwesome = () => (+((Math.random() * 10000).toString().split('.')[0]))


const doTheThing = () => {
  console.log(`This is awesome! ${getAwesome()}`)
}

doTheThing() // prints

vs

/*
Function declarations are given two passes, where the first lifts them to
the top of the namespace, allowing "out of order" usage
*/

doTheThing();


function doTheThing() {
  console.log(`This is awesome number ${getAwesome()}`)
}

function getAwesome() {
  return (+((Math.random() * 10000).toString().split('.')[0]))
}
Wayne Bloss
  • 558
  • 4
  • 6
  • It looks like the undefined reference of `getAwesome` is caught, at least when I try it in JS fiddle. https://jsfiddle.net/6f05vkj8/1/ I don't understand what you mean when you say it fails silently. I agree hoisting makes this code work, but that doesn't necessarily make it better, there is clarity to things unavailable till declared, especially when only some things are. The biggest thing is that the functions are named in the call stack, which is a big problem with const declarations. – Daniel Brotherston Jan 19 '21 at 00:14
  • @DanielBrotherston The other guy said "function hoisting can break conflicting scripts in ways that are difficult to debug as it fails silently", I didn't say anything about that. Regarding the point of hoisting though - I don't see a problem with functions being hoisted within a module. Everybody is using modules via `import` or `require` these days right? So it's not like we're hoisting functions into `global` like the days of yore when we were concatenating raw script files instead of compiling them with Babel or TypeScript or using Node.js. – Wayne Bloss Jan 30 '21 at 19:30
  • @WayneBloss I was referring to your comment `This shows that, because of block-scoping, const function references must be invoked in-order or else things will fail silently.` in the comment in the first code example. As for hoisting, yes, it is less likely now, but even within modules, I still prefer the simpler behaviour of declare before use. – Daniel Brotherston Jan 30 '21 at 20:47
  • 1
    @DanielBrotherston Ahhh, I see - yes that was a comment from the source git. I guess it was wrong. Perhaps I'll remove it. – Wayne Bloss Jan 30 '21 at 21:39
  • Some other places on the internet had confused me about this, but the example makes it clear that in JS, a function declaration isn't just the signature (like in C), it includes the function body as well. [`let` and `const` are also hoisted](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting#let_and_const_hoisting), but only the declaration (the part to the left of the `=`), not the initializer (the function expression to the right of the `=`). Thus, they don't have valid values until the initializer executes. – andyg0808 Mar 08 '22 at 02:52