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?

5
  • 3
    This question on StackOverflow may interest you: stackoverflow.com/questions/34361379/… 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
    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
    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
    Jan 16, 2018 at 20:00
  • 1
    Long story short: You should value clear and concise over an edge-case benefit. Jul 10, 2019 at 15:04

2 Answers 2

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.

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
    Jan 17, 2018 at 17:55
  • 5
    Just a note that eslint no-func-assign may catch this redeclaration issue.
    – user949300
    Jan 26, 2018 at 1:18
  • 17
    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. Jul 10, 2019 at 14:54
  • so, what should I use at the end ? Jan 26, 2022 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 at 15:49
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]))
}
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. 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. 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. 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. Jan 30, 2021 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, 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 8, 2022 at 2:52

Your Answer

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

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