👋 Hi! I'm Dastan, author of Hack Frontend. Always open to professional networking => connect with me on LinkedIn.
Arrow Functions vs Function Declaration vs Function Expression
In JavaScript there are several ways to declare functions: Function Declaration, Function Expression and Arrow Function. Each has its own features regarding hoisting, this usage, ability to be a constructor, and nuances in syntax and application.
Brief Comparison Table
| Characteristic | Function Declaration | Function Expression | Arrow Function |
|---|---|---|---|
| Syntax | function name() { ... } | const name = function() { ... } | const name = () => { ... } |
| Hoisting | Full (the function can be called before its declaration) | The function itself does not hoist. var: the variable is undefined until assignment. const/let: TDZ (ReferenceError) until initialization | Same as an expression: it depends on var, const, or let, not on arrow syntax |
Own this | Yes (depends on call) | Yes (depends on call) | No (lexical this, inherited from outer scope) |
arguments | Available | Available | No (use ...rest) |
Use with new | Yes (can act as constructor) | Yes (if declared as regular function) | No (Arrow Function can't be constructor) |
| Application | General functions, constructors | Closures, callbacks | One-liners, callbacks |
Details and Examples
Hoisting
-
Function Declaration fully hoists. This means we can call such function even before its actual declaration in file.
hello(); // "Hello from Declaration!" function hello() { console.log("Hello from Declaration!"); } -
A Function Expression does not hoist as a ready function. Only the variable binding is created, and access before initialization depends on
var,let, orconst.With
var, the variable hoists and isundefineduntil assignment: calling it throws aTypeError. Withconstorlet, access before the initialization line hits the temporal dead zone and throws aReferenceError.console.log(typeof sayHiVar); // "undefined" // sayHiVar(); // TypeError: sayHiVar is not a function var sayHiVar = function () { console.log("Hello from Expression!"); }; // sayHi(); // ReferenceError: Cannot access 'sayHi' before initialization const sayHi = function () { console.log("Hello from Expression!"); }; -
An Arrow Function follows the same hoisting rules as a function expression: the variable hoists, not the arrow itself. With
const/letyou get a TDZ before initialization; withvarthe value isundefined.// greet(); // ReferenceError: Cannot access 'greet' before initialization const greet = () => console.log("Hello from Arrow!");
this Context
-
Function Declaration and Function Expression (if not arrow) have
thiscontext determined by call method (or .call, .apply, .bind binding). -
Arrow Function doesn't have own
this: it's lexically borrowed from scope where arrow function is declared.const user = { name: "Alice", sayHello: function() { console.log("Hello, I'm " + this.name); }, arrowHello: () => { console.log("Hello, I'm " + this.name); } }; user.sayHello(); // "Hello, I'm Alice" - this = user user.arrowHello(); // "Hello, I'm undefined" - this taken from outer scope (global / undefined)
arguments and ...rest Operator
-
Function Declaration and Function Expression have pseudo-array
argumentscontaining all function arguments. -
Arrow Function doesn't have
arguments, but you can use...restoperator to get all arguments.function sumAll() { let total = 0; for (const arg of arguments) { total += arg; } return total; } const sumAllArrow = (...args) => { return args.reduce((acc, curr) => acc + curr, 0); };
Using with new
- Only Function Declaration and Function Expression (not in arrow format) can be constructors.
- Arrow Function can't be used with
newas it doesn't have internal[[Construct]]mechanism.