var functionName = function() {} vs function functionName() {}

The difference is when the function exists. Everything else — the name, the error message, what happens in a block — follows from that.

Hoisting

declared() before its line : works
expressed() before its line: TypeError: expressed is not a function
expressed() after its line : works

A function declaration is available in its entire scope, body and all, before the line it is written on. A function expression is an assignment: the variable is hoisted, the function is not.

Read the error again — “is not a function”, not “is not defined”. expressed exists at that point and holds undefined. That wording is the fingerprint of this mistake.

var and const fail differently

var   : TypeError: withVar is not a function
const : ReferenceError: Cannot access 'withConst' before initialization

Same mistake, two errors. var is hoisted and initialised to undefined; const and let are hoisted but left uninitialised — the temporal dead zone. The const message is the more useful of the two, which is a small argument for const over var on top of the usual ones.

The name

const anon = function(){}        .name : "anon"
const named = function myName(){}.name : "myName"
function decl(){}                .name : "decl"
({ m: function(){} }).m.name          : "m"
[function(){}][0].name                : ""

The “anonymous” function is not anonymous. Since ES2015 the engine infers a name from the binding it is assigned to — a variable name, an object key. Only where there is nothing to infer from, such as a function literal inside an array, is the name really empty.

This matters because of the advice that comes with the question:

inferred : at boom1 (file:///app/demo.mjs:30:35)
explicit : at reallyBoom (file:///app/demo.mjs:31:45)

“Always name your function expressions so stack traces are readable” was good advice and is now mostly obsolete: the inferred name is already in the trace. Keep the habit for the array-literal and callback cases where there is no binding.

A named function expression’s name is not a variable, though:

inner(5) called from within itself : 120
typeof inner from outside          : undefined

The name is in scope inside the function and nowhere else — which is what makes it useful for recursion without depending on the outer variable.

Inside a block

typeof inBlock after the block, in a MODULE : undefined

In a module — that is, in strict mode — a declaration inside { } is scoped to that block. In a sloppy-mode script the same code leaks the function to the enclosing function scope instead. Same source, different result depending on how the file is loaded, which is a good reason to keep declarations out of blocks.

Duplicates

sloppy script, two declarations of dup() -> second
the same source as a MODULE : SyntaxError: Identifier 'd' has already been declared
two consts       : SyntaxError: Identifier 'c' has already been declared

Two declarations of the same name in a sloppy script: the second silently wins, and if the two are 400 lines apart you will spend an afternoon on it. In a module it is a SyntaxError before anything runs — the file does not load at all.

I found this by writing the harness for this article with two function dup() declarations in it and watching it refuse to start.

Arrow functions

obj.classic() : 42
obj.arrow()   : undefined <- arrow has no own this
detached classic() : undefined <- this is undefined in a module
new on an arrow    : TypeError: (intermediate value) is not a constructor
arguments in an arrow : object vs undefined

An arrow function is an expression too, so it hoists like one, and it differs again: no own this, no arguments, and it cannot be called with new. Those are the reasons not to use one as an object method or a constructor — and the reasons to use one as a callback, where inheriting this is exactly what you want.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).