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, ...).