Zademy

Ways to Declare Functions in JavaScript: Technical and Practical Guide

JavaScript; functions
words words

Traditional function declaration (function declaration)

This is the most classic form. You use the function keyword followed by a mandatory name. The technical feature that sets it apart is hoisting: you can call the function in lines of code that appear before its physical definition in the file.

Example with parameters:

function greet(name) {
	return `Hello, ${name}!`;
}
console.log(greet("World")); // "Hello, World!"

Function expression

Here, the function is treated as a data value assigned to a variable. You can use let, var or const to declare that variable. Unlike declarations, these are not fully subject to hoisting. The function does not exist until execution reaches the assignment line.

  • Anonymous: It has no explicit name.
  • Named: It includes an internal identifier useful for recursion and debugging (it shows up in the stack trace).

Example:

const sum = function (a, b) {
	return a + b;
}; // Using 'const' helps avoid accidental reassignments

Arrow functions

Introduced in ES6, arrow functions give you a compact syntax and remove the need for the function keyword. The biggest semantic difference: they do not have their own this binding. Instead, they capture this from the surrounding lexical scope.

  • Short syntax: If you have only one parameter and one line of code, you can omit parentheses, braces, and the explicit return.

Example:

const square = x => x * x; // Implicit return

Method definition in objects and classes (shorthand)

This is a shorthand syntax for declaring functions inside object literals or classes, omitting the colon and the function keyword. These methods are the only ones that can access the super binding.

Example in an object:

const calculator = {
	value: 10,
	double() {
		return this.value * 2;
	} // Clean syntax
};

Async functions (async)

Marked with the async prefix, these functions always return a Promise. They allow the use of await inside their body to pause execution in a non-blocking way until a promise resolves.

Example:

async function fetchData() {
	const response = await fetch("url");
	return response.json();
}

Generator functions (function*)

Defined with function*, generators allow non-continuous execution. They can pause their state and yield control back to the caller using the yield keyword.

Example:

function* counter() {
	yield 1;
	yield 2;
}

Immediately Invoked Function Expressions (IIFE)

These are functions that run as soon as they are defined. They are mainly used to create a private scope and avoid polluting the global namespace.

Example:

(function () {
	const message = "Private";
	console.log(message);
})();

The Function constructor lets you create functions dynamically from strings at runtime. I'd avoid it. It carries security risks and JavaScript engines optimize it poorly.

Example:

const dynamicSum = new Function("a", "b", "return a + b");

An analogy for the differences

Think of a function declaration as a recipe carved in stone in the kitchen: it has always been there and everyone knows where to find it, even before the shift starts. A function expression is like a sticky note you write on the spot. You can only follow its instructions after you have written it and stuck it to the wall. An arrow function is like a mental shortcut that uses the context of what you are already doing to complete a quick task, no full recipe needed.