65

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?

5
  • 3
    This question on StackOverflow may interest you: stackoverflow.com/questions/34361379/… Commented Jan 16, 2018 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
    Commented Jan 16, 2018 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
    Commented Jan 16, 2018 at 19:44
  • 3
    @user949300 There is a difference, the one MetaFight mentions. Also, protype / "this stuff" quickly become critical distinctions, as well.
    – msanford
    Commented Jan 16, 2018 at 20:00
  • 1
    Long story short: You should value clear and concise over an edge-case benefit. Commented Jul 10, 2019 at 15:04

2 Answers 2

50

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.

6
  • 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
    Commented Jan 17, 2018 at 17:55
  • 5
    Just a note that eslint no-func-assign may catch this redeclaration issue.
    – user949300
    Commented Jan 26, 2018 at 1:18
  • 21
    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. Commented Jul 10, 2019 at 14:54
  • so, what should I use at the end ? Commented Jan 26, 2022 at 12:43
  • 1
    @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
    Commented May 17, 2023 at 16:34
53

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]))
}
5
  • It looks like the undefined reference of getAwesome is caught, at least when I try it in JS fiddle. 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. Commented Jan 19, 2021 at 0: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. Commented Jan 30, 2021 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. Commented Jan 30, 2021 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. Commented Jan 30, 2021 at 21:39
  • 1
    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, 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
    Commented Mar 8, 2022 at 2:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.