JavaScript: A Comprehensive Guide to Functions
The all you need to know about Functions in JavaScript
I. INTRODUCTION
Imagine building a house without tools. Sure, you might stack bricks, but shaping, refining, and assembling everything seamlessly would be nearly impossible. In JavaScript, functions are those essential tools—the backbone of every task, from handling user clicks to fetching data behind the scenes. When you opened this article, multiple functions quietly orchestrated the process: an API call fetched content, another rendered it on your screen, and yet another handled potential errors.
But functions in JavaScript are more than just task executors. They can be stored in variables, passed as arguments, and even returned by other functions—transforming them into powerful, reusable building blocks. In this comprehensive guide, we'll unravel JavaScript functions from the ground up, progressing from the fundamentals to advanced concepts that empower modern web development.
What is a function actually is?
In JavaScript, functions are reusable blocks of code that perform specific tasks. They can take inputs, process them, and return outputs.
In JS functions takes parameters(also called arguments). They are dynamic values which are by default set to undefined.
These parameters act as placeholders for the actual values passed when the function is called.
When we call a function JavaScript follow these steps:
Declaration: JavaScript stores the function definition in memory.
Invocation: When the function is called, the engine creates an execution context.
Parameter Assignment: Arguments passed during the call are assigned to the function's parameters.
Execution: JavaScript runs the code inside the function body.
Return: When a function doesn't explicitly return a value, it implicitly returns
undefined
.Context Cleanup: After execution, the function context is removed from memory.
II. Function Declarations vs Function Expressions
#Function Declarations
A function definition (also called a function declaration, or function statement) consists of the function
keyword, followed by:
The name of the function.
A list of parameters to the function, enclosed in parentheses and separated by commas.
The JavaScript statements that define the function,
enclosed in curly braces,
{ /* … */ }
.
For example
function zero(number){
return number * 0
}
The function zero
takes one parameter, called number
.
The function consists of one statement that says to return the parameter of the function (that is, number
) multiplied by zero. The return
statement specifies the value returned by the function, which is number * 0
.
Parameters are essentially passed to functions by value — so if the code within the body of a function assigns a completely new value to the parameter that was passed to the function
the change is not reflected globally neither in the code which called that function.
The arguments of a function are not limited to strings and numbers. You can pass whole objects to a function
Passing Arrays and Objects
When you pass an object as a parameter in a function, the change of properties by body a function to the passed parameter which is object will took the place outside of function
As you can see in the following code:
function myFunc(theObject) {
theObject.make = "Toyota";
}
const myCar = {
make: "Honda",
model: "Accord",
year: 1998,
};
console.log(myCar.make); // "Honda"
myFunc(myCar);
console.log(myCar.make); // "Toyota"
When an array is passed as a parameter in a function,
Objects like arrays, are passed by reference. Changes inside the function affect the original object.
Example:
function addFruit(arr) {
arr.push("Mango");
}
const fruits = ["Apple", "Banana"];
addFruit(fruits);
console.log(fruits); // Output: ["Apple", "Banana", "Mango"]
You can destructure arrays and objects directly in the function parameters.
function printNames([first, second, third]) {
console.log(`First: ${first}, Second: ${second}, Third: ${third}`);
}
const vehicles= ["Cycle", "Van", "Truck"];
printNames(vehicles);
// Output: First: Cycle, Second: Van, Third: Truck
This helps in code readability and avoids repetitive dot notation (user.name
, user.age
).
Function Expressions
While the function declaration above is syntactically a statement, functions can also be created by a Function Expressions.
Such a function can be anonymous; it does not have to have a name. For example, the function zero
could have been defined as:
const zero = function(number){
return number * 0;
}
console.log(zero(4)); // 0
Encapsulation—Anonymous functions avoid polluting the global scope with unnecessary function names.
Function expressions are convenient when passing a function as an argument to another function.
Function expressions are convenient when passing a function as an argument to another function. The following example defines a map
function that should receive a function as first argument and an array as second argument.
Then, it is called with a function defined by a function expression:
// Define the custom map function
function customMap(func, array) {
const result = [];
for (let i = 0; i < array.length; i++) {
result.push(func(array[i])); // Apply the passed function to each array element
}
return result;
}
// Use an anonymous function expression as the callback
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = customMap(function (num) {
return num * num;
}, numbers);
console.log(squaredNumbers); // Output: [1, 4, 9, 16, 25]
In JavaScript functions can be assign based on a condition. For example,
let myFunc;
if (num === 0) {
myFunc = function (theObject) {
theObject.make = "Toyota";
};
}
Calling Functions
Defining the function does not mean to execute it means the name and specifies what to do when the function is called.
Calling the function actually performs the specified actions with the indicated parameters. For example, if you define the function zero
, you could call it as follows:
zero(137)
The preceding statement calls the function with an argument of 137
. The function executes its statements and returns the value 0
.
Functions must be in scope when they are called, but the function declaration can be hoisted (appear below the call in the code).
Function Hoisting
See the code below:
greet(); // ✅ Works! Output: Hello!
function greet() {
console.log("Hello!");
}
The above code will outputs correctly because JavaScript knows about greet keyword before encountering the definition.
sayHello(); // ❌ Error: Cannot access 'sayHello' before initialization
const sayHello = function () {
console.log("Hello!");
};
Since, it is hoisted using a variable assignment the above code will throw error because in function expression you declare the variable only not the function itself. Therefore, accessing it before the assignment throws an error.
Function hoisting only works with function declarations — not with function expressions. The following code will not work:
// Hoisting behavior
function greet() {
console.log("Hello!");
}
const sayHello; // Only declaration is hoisted, not the assignment
greet(); // Works
sayHello(); // Error: Not defined yet
sayHello = function () {
console.log("Hello!");
};
Recursion
Recursion, occurs when a function calls itself to solve a problem by breaking it into smaller sub-problems.
Each recursive call has its own execution context, and the process continues until it reaches a base case—a condition where the recursion stops.
Basically; A function can refer to and call itself additionally it is similar to like a loop.
Likewise, You clean a room. Each drawer has more compartments. You open one, find another inside, clean it, and so on.
function factorial(n) {
if (n === 0) {
return 1; // Base case: Stop recursion when n reaches 0
}
return n * factorial(n - 1); // Recursive call
}
console.log(factorial(5)); // Output: 120
IIFE (Immediately Invoked Function Expression)
Furthermore, An IIFE is a function that runs immediately after being defined. It’s commonly used to create a private scope and avoid polluting the global namespace.
It looks like this:
(function () {
// Do something
})();
const value = (function () {
// Do something
return someValue;
})();
This is almost equivalent to just writing the function body, but there are a few unique benefits:
It creates an extra scope of variables, which helps to confine variables to the place where they are useful.
It is now an expression instead of a sequence of statements. This allows you to write complex computation logic when initializing variables.
Arrow Functions (ES6 and Beyond)
ECMAScript 2015 also known as ES6 was released in 2015 in which lot major changes were made and came arrow function is one of them.
An arrow function expression is a compact alternative to a traditional function expression, with some semantic differences and deliberate limitations in usage:
Arrow functions don't have their own bindings to
this
,arguments
, orsuper
, and should not be used as methods.Arrow functions cannot be used as constructors. Calling them with
new
throws aTypeError
. They also don't have access to thenew.target
keyword.When the body has a single expression, the result is automatically returned.
Arrow functions cannot use
yield
within their body and cannot be created as generator functions.They works only if the function has only one statement. If you have parameters, you pass them inside the parentheses:
Arrow functions don’t have their own
arguments
object.They are always anonymous.
const materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
console.log(materials.map((material) => material.length));
// Expected output: Array [8, 6, 7, 9]
Scopes and Closures
Functions form a scope for variables—this means variables defined inside a function cannot be accessed from anywhere outside the function. The function scope inherits from all the upper scopes.
For example, a function defined in the global scope can access all variables defined in the global scope.
A function defined inside another function can also access all variables defined in its parent function, and any other variables to which the parent function has access.
On the other hand, the parent function (and any other parent scope) does not have access to the variables and functions defined inside the inner function.
This provides a sort of encapsulation for the variables in the inner function.
// The following variables are defined in the global scope
const num1 = 20;
const num2 = 3;
const name = "Chamakh";
// This function is defined in the global scope
function multiply() {
return num1 * num2;
}
console.log(multiply()); // 60
// A nested function example
function getScore() {
const num1 = 2;
const num2 = 3;
function add() {
return `${name} scored ${num1 + num2}`;
}
return add();
}
console.log(getScore()); // "Chamakh scored 5"
In JavaScript, a closure occurs when a function retains access to variables from its outer scope, even after the outer function has finished executing.
This happens because JavaScript uses lexical scoping, meaning functions can access variables defined in their parent scopes.
When an inner function is created inside an outer function, it forms a closure by "remembering" the environment in which it was created.
Even if the outer function returns, the inner function continues to have access to the outer variables. This is possible because JavaScript doesn't discard the variables from memory as long as the inner function still references them.
Closures are widely used for data encapsulation, allowing the creation of private variables that cannot be accessed directly from outside the function.
For example, a counter function can maintain its count privately using a closure.
function createCounter() {
let count = 0; // Private variable
return {
increment: function () {
count++;
console.log(count);
},
decrement: function () {
count--;
console.log(count);
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
They are also useful in callbacks, event handlers, and function factories,
providing a powerful way to manage state and create specialized behavior.
However, while closures enhance functionality, improper use can lead to memory leaks if references to large objects persist unnecessarily.
function outerFunction() {
let outerVar = "I'm from the outer scope";
function innerFunction() {
console.log(outerVar); // Accessing outer function's variable
}
return innerFunction;
}
const closureExample = outerFunction();
closureExample(); // Output: I'm from the outer scope
Conclusion
Functions in JavaScript are a block of code that performs a specific task, can accept arguments(parameters), return outputs, and be reused throughout a program. We have two ways to define a function one through using function keyword while another by defining variable to the functions. Both react to hoisting in a different manner.