Ways to declare functions
In JavaScript a function is a value, so the language offers several syntactic forms for creating one, from the classic function to arrows, generators and class methods. The forms differ in more than looks: the choice decides hoisting, how this behaves, whether prototype exists and whether the function can be called with new.
Theory
TL;DR
- A function declaration is hoisted, has its own
thisand can be a constructor. - A function expression is available only after the assignment, and can be anonymous or named.
- An arrow function takes
thislexically, has noarguments, noprototypeand does not work withnew. - Special kinds:
new Function(built from a string, not recommended),function*(generator),async function(returns aPromise). - Object and class methods are functions too, but with
supersupport through[[HomeObject]]. - IIFE and
bind()are not new syntactic forms, they are ways to run a function immediately or to fix itsthis.
Quick example
function sum(a, b) { // 1. function declaration
return a + b;
}
const mul = function (a, b) { // 2. function expression
return a * b;
};
const inc = x => x + 1; // 3. arrow function
const obj = {
sayHi() { console.log('hi'); } // 4. method shorthand
};Classic forms: declaration, expression, arrow
1) Function declaration
function sum(a, b) {
return a + b;
}- It is hoisted: the function can be called before its declaration in the code.
- It has its own
thiswhen called as a function or as a method. - It can be used as a constructor (
new Sum()).
2) Function expression
const mul = function (a, b) {
return a * b;
};- No hoisting: the function is available only after the assignment.
- It can be anonymous or named:
const fact = function factorial(n) { /* ... */ }; // named: the name is visible insideThe name of a named expression is visible only inside the function itself, which is handy for recursion and for a readable stack trace.
3) Arrow function
const inc = x => x + 1;
const sum = (a, b) => a + b;- Lexical
this: it is taken from the outer lexical environment. - No
arguments(use rest parameters...argsinstead). - Cannot be used with
new, has noprototype. - A great fit for callbacks and short functions.
Special kinds: Function, generators, async
4) Function constructor
const fn = new Function('a', 'b', 'return a + b');- Creates a function from a string, just like
eval. - Not recommended: security and performance problems, plus the body is compiled in the global scope.
5) Generator function (function*)
function* idGenerator() {
let i = 0;
while (true) {
yield i++;
}
}- Returns an iterator and supports
yield. - Execution can be paused and resumed.
6) Async function
async function fetchData() {
const r = await fetch('/api');
return r.json();
}
const fn = async () => { /* an async arrow works too */ };- Always returns a
Promise. - You can use
awaitinside it.
Combinations are allowed too: async function* is an async generator for for await...of.
Object and class methods
7) Method shorthand in objects
const obj = {
sayHi() { console.log('hi'); }, // method
async load() { await fetch('/'); } // async method
};- Convenient for methods: they have
[[HomeObject]], which meanssuperworks. - You can also write
prop: function () {}, but that is a plain function expression with nosupersupport.
8) Class methods (inside class)
class C {
method() {}
static staticMethod() {}
async asyncMethod() {}
}- A class declaration is not hoisted the way a function declaration is: before the
classline runs, the class cannot be used. - Methods are used with instances or with the class itself and they support
super. - A class body always runs in strict mode.
Derived forms: IIFE and bind
9) IIFE, Immediately Invoked Function Expression
(function () {
// runs immediately
})();- Useful for creating a local scope; before modules existed this was the main way to keep the global scope clean.
10) Function.prototype.bind (creating a bound function)
function f() { console.log(this); }
const bound = f.bind({ x: 1 });- Returns a new function with a fixed
thisand, optionally, with partially applied arguments. - Binding an already bound function again does not change the context.
Common mistakes
- Relying on hoisting for a function expression: calling
mul(2, 3)aboveconst mul = function ...throws aReferenceError. - Declaring an object method as an arrow and expecting
thisto point at that object. - Using
new Functionor a string instead of a normal function: it carries the same risk aseval. - Forgetting that an
asyncfunction returns aPromiseand reading its result withoutawaitor.then(). - Confusing
prop: function () {}with the shorthandprop() {}: the first has no[[HomeObject]], sosuperdoes not work in it. - Declaring a function declaration inside an
ifblock and expecting consistent behaviour across environments: use an expression instead.
Comparing the forms
| Form | Hoisting | Own this | new | prototype |
|---|---|---|---|---|
| Function declaration | Yes | Yes | Yes | Yes |
| Function expression | No | Yes | Yes | Yes |
| Arrow function | No | No (lexical) | No | No |
new Function | No | Yes | Yes | Yes |
Generator function* | Yes (for a declaration) | Yes | No | Yes |
| Async function | Yes (for a declaration) | Yes | No | No |
| Object or class method | No | Yes | No | No |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.