JavaScript, the cornerstone of modern web applications, thrives on its powerful and flexible function capabilities. Functions in JavaScript allow developers to create reusable code blocks, encapsulate functionality, and design modular applications. Here’s a comprehensive look into the world of JavaScript functions.
Definition and Declaration
A JavaScript function is a block of reusable code designed to execute a particular task. They can be invoked (called) multiple times with different arguments to produce various results. Functions can also be assigned to variables, passed as arguments, and returned from other functions, making them first-class citizens in the language.
In JavaScript function can be written in different ways though the calling would be same for each variation. An example of traditional way of writing functions in JavaScript is as follows
function sayHello(name) {
return "Hello, " + name + "!";
}
Here “sayHello” is the name of the function and “name” is a parameter which has been passed to the function. The function returns a string.
Calling a function
Once declared, a function does not execute on its own. It needs to be called or invoked.
sayHello("John"); // Hello, John
Function Expression
A function expression is a way to define a function as part of an expression rather than a standalone declaration. This often involves assigning a function to a variable.
A typical function expression looks like this:
const functionName = function(parameters) {
// function body
};
The function does not have a name to the left of the parameters and the function keyword. This is why it’s sometimes called an anonymous function. However, you can also have named function expressions:
const functionName = function myFunc(parameters) {
// function body
};
In the above example, “myFunc"
is the name of the function, but it’s only available within the body of the function itself.
One important thing to note about function declaration is unlike function declarations, function expressions are not hoisted. Therefore, you cannot use the function before it’s defined. An example will keep things more clearer
hello(); // TypeError: hello is not a function
const hello= function() {
console.log("Hello!");
};
To know more about hoisting you can check the following article and look for “Difference between var, let and constants in JavaScript”
Javascript/Typescript/Node.js Interview Questions Part -1 JavaScript questions
Arrow Function (ES6)
Arrow functions, introduced in ECMAScript 6 (ES6), provide a new, concise syntax to declare functions in JavaScript. Apart from the abbreviated syntax, arrow functions also have distinctive behavior when it comes to the handling of the this
keyword. We will check in detail about arrow functions in this section
An arrow function is defined using a set of parentheses containing the arguments, followed by a fat arrow =>
and then the function body.
const sum = (a, b) => a + b;
If the function body contains more than one expression, you can use curly braces:
const sayHello = name => {
const message = "Hello, " + name + "!";
console.log(message);
};
If a function is returning the
Parentheses and Parameters
Single Parameter: Parentheses are optional.
const square = x => x * x;
No Parameter or multiple Parameter : Parentheses are required.
const greetAll = () => console.log("Hello, everyone!");
const add = (a, b) => a + b;
Implicit Return: Short and Sweet
Arrow functions offer another convenience: implicit return. If the function body consists of a single expression, you can omit the curly braces and the return
keyword. The expression’s result will be automatically returned:
// Regular Function with Explicit Return
const double = function(x) {
return x * 2;
};
// Arrow Function with Implicit Return
const doubleArrow = x => x * 2;
Arrow Function and ‘this’ keyword
In JavaScript, the this keyword inside a function refers to the object that the function is a property of or the object that the function is called on. However, the behavior of the this keyword inside an arrow function is different from regular functions. In an arrow function, the this keyword is lexically scoped, meaning it takes on the value of the this keyword in the surrounding code. The this keyword in an arrow function does not get rebound when the function is invoked, unlike regular functions. It keeps the same value as the this keyword in the surrounding code.
const obj ={
name: "Joe",
sayHello: ()=>{
console.log(`Hello my name is ${this.name}`);
}
}
obj.sayHello(); // Output: undefined
const objarrow = {
name:"Jane",
sayHello(){
console.log(`Hello my name is ${this.name}`);
}
}
objarrow.sayHello(); // Output: Hello my name is Jane
Closures
A closure is a function bundled with references to its surrounding state, the lexical environment. This means, even after the outer function has completed its execution, the inner function still retains access to its surrounding scope. In simpler terms, closures allow JavaScript functions to have “private” variables.
Let’s see in more detail with an example
function outerFunction() {
let count = 0;
return function innerFunction() {
count++;
return count;
}
}
const counter = outerFunction();
console.log(counter()); // Outputs: 1
console.log(counter()); // Outputs: 2
In the above code:
outerFunction
returns theinnerFunction
.innerFunction
accesses thecount
variable, which is in the scope ofouterFunction
.- Every time
counter
(an instance ofinnerFunction
) is invoked, it has access to thecount
variable, even thoughouterFunction
has finished its execution.
This ability of innerFunction
to “remember” the environment in which it was created is the essence of closures.
Usage of Closure
Data encapsulation
By using closures, developers can mimic private methods, a concept from object-oriented programming. This encapsulation ensures that certain variables are not directly accessible from outside the function, preventing unintended modifications.
function bankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
balance += amount;
return balance;
},
withdraw: function(amount) {
balance -= amount;
return balance;
}
};
}
const account = bankAccount(100);
account.deposit(50); // 150
account.withdraw(20); // 130
Here balance
variable can’t be directly modified from outside the function hence encapsulated
Creating Factory Functions:
Closures allow developers to create factory functions—functions that return other functions with specific configurations.
function greetingFactory(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
}
}
const sayHello = greetingFactory('Hello');
const sayHi = greetingFactory('Hi');
sayHello('Alice'); // Outputs: "Hello, Alice!"
sayHi('Bob'); // Outputs: "Hi, Bob!"
JavaScript Functions are First Class Citizens
What is a First Class Function ?
A Programming language said to have First Class Functions if the functions in that programming language can be treated like any other variable or object. Which means that the functions can be assigned to any other variable, can be passed as a parameter to another function and can be returned from other functions
In JavaScript we can do the same with the functions so it is regarded as a “First Class Citizen” in the language
Let’s check each of the scenario in more details with examples
1. Assigned to variables: You can assign a function to a variable like any other value.
const greet = function(name) {
console.log(`Hello, ${name}!`);
};
greet('John'); // Calls the function using the variable
2. Passed as arguments to other functions: You can pass functions as arguments to other functions.
function calculate(func, a, b) {
return func(a, b);
}
function add(x, y) {
return x + y;
}
function subtract(x, y) {
return x - y;
}
console.log(calculate(add, 5, 3)); // 8
console.log(calculate(subtract, 10, 2)); // 8
3.Returned from other functions: Functions can also return other functions.
function multiplier(factor) {
return function(x) {
return x * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // 10
4.Stored in data structures: Functions can be stored in arrays or objects just like any other values.
const mathOperations = {
add: function(x, y) {
return x + y;
},
subtract: function(x, y) {
return x - y;
}
};
console.log(mathOperations.add(10, 5)); // 15
console.log(mathOperations.subtract(10, 5)); // 5
Function Currying
Function currying in JavaScript is a technique that allows you to transform a function that takes multiple arguments into a sequence of functions, each taking a single argument. JavaScript provides native support for currying through closures and the use of higher-order functions like bind
or manually crafting curried functions.
In simpler words let’s say there is a function call mul(1,2,3) and after currying the function call will be like mul(1)(2)(3)
Lets look at the normal function with multiple arguments
function mul(x,y,z){
return x*y*z;
}
console.log(x,y,
We can convert the above function to a curried function like the following
function mul(x){
return function(y){
return function(z){
return x*y*z;
}
}
}
console.log(mul(2)(3)(4)) // Output: 24
Function currying empowers you to create flexible and reusable functions, making your code more modular and easier to maintain. It’s a valuable technique, especially in functional programming and when working with higher-order functions.
Higher Order Function
A Higher Order function is a function which accepts a function as an argument and/or return a function as an argument. Higher Order functions are only possible if the language itself having the concept the First Class function.
Let’s check with some examples
const arr = [1,2,3]
const double = x => x*2;
const doublearr = arr.map(double);
console.log(doublearr); // Output: [2,4,6]
In the above example map
is an inbuilt JavaScript function which takes another function as an argument . So map can be considered as Higher Order function.
const increaseby= (num)=>{
return function(increment){
return num+increment;
}
}
console.log(increaseby(4)(2))// Output: 6
console.log(increaseby(4)(3))//Output: 7
Here the increaseby function is a Higher Order function because it returns a function
Callback Function
A callback function is a function passed as an argument to another function, which is then invoked within the outer function. Callbacks are commonly used for asynchronous operations like handling AJAX requests, timers, and event handling.
Let’s check some examples to make our idea clear about callbacks
function fetchData(callback) {
setTimeout(function () {
const data = "Async data fetched";
callback(data);
}, 2000);
}
function processFetchedData(data) {
console.log(`Processing: ${data}`);
}
fetchData(processFetchedData); // Outputs "Processing: Async data fetched" after 2 seconds
In this example, fetchData
is an asynchronous function that accepts a callback processFetchedData
. Once the data is fetched, it invokes the callback to process the result.
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(function (num) {
return num ** 2;
});
console.log(squaredNumbers); // Outputs [1, 4, 9, 16, 25]
The map
function takes a callback that is applied to each element in the numbers
array, transforming them into squared numbers.
Recursive Functions
Recursive functions are functions that call themselves to solve a problem by breaking it down into smaller, similar subproblems. In JavaScript, recursion is a powerful technique often used for tasks that can be naturally divided into smaller, repetitive tasks. Here’s a note with examples of recursive functions:
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
console.log(factorial(5)); // Outputs 120 (5! = 5 * 4 * 3 * 2 * 1)
In this example, the factorial
function calculates the factorial of a number using recursion. It breaks down the problem into smaller subproblems until it reaches the base case (n equals 0 or 1), then builds the solution from there.
In conclusion, functions are the building blocks of JavaScript, and understanding their nuances is crucial for mastering the language. In this article, we’ve explored the fundamentals of functions, including their syntax, parameters, return values, and scopes. We’ve delved into advanced concepts like closures, first class functions, higher-order functions, function currying, callbacks and recursive functions etc. which empower developers to write more expressive, modular, and efficient code. Whether you’re a beginner learning the basics or an experienced developer looking to deepen your JavaScript knowledge, a solid grasp of functions is essential. Happy Coding!!