DEV Community

Cover image for Mastering JavaScript Functions: The Core Foundation
Debraj Roy
Debraj Roy

Posted on

Mastering JavaScript Functions: The Core Foundation

What is a Function

Before delving into the concept of functions and how they operate, let's illustrate the need for them with a problem scenario.

Problem:
Suppose we want to identify all the even numbers within a given range.

let num1 = 14;
console.log("finding all the even numbers within", num1);
for (let i = 0; i <= num1; i++) {
  if (i % 2 === 0) console.log(i);
}
let num2 = 19;
console.log("finding all the even numbers within", num2);
for (let i = 0; i <= num2; i++) {
  if (i % 2 === 0) console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

The code above demonstrates a straightforward approach to this task, but it exhibits redundancy. Each time we want to identify even numbers within a different range, we find ourselves rewriting similar loops, violating the DRY (Don't Repeat Yourself) principle.

To overcome this redundancy, we require a mechanism that streamlines our process. Enter functions.

A function in JavaScript is a reusable block of code designed to perform a specific task. It serves as a fundamental building block of the JavaScript language, allowing for modular and organized code. Functions can be called multiple times without the need for repetition, enhancing code efficiency and maintainability.

JavaScript comes with several predefined functions, such as alert(), prompt(), and confirm(), which are commonly used for interacting with users.

To utilize a function effectively, there are two primary steps involved:

  1. Create a Function Definition: This step involves defining the function, specifying its name, parameters (if any), and the code block that comprises its functionality. Here's a basic example:
function greet(name) {
    console.log("Hello, " + name + "!");
}
Enter fullscreen mode Exit fullscreen mode
  1. Call a Function: Once the function is defined, it can be called or invoked at any point within the code to execute the specified task. To call a function, simply use its name followed by parentheses, optionally passing any required arguments. For instance:
greet("John"); // Output: Hello, John!
Enter fullscreen mode Exit fullscreen mode

These two steps encapsulate the process of using functions in JavaScript, enabling developers to organize code efficiently and perform tasks repeatedly with minimal redundancy.

Function Definition and Invoking

Syntax:

function getEvenNumber(number) {
  console.log("finding all the even numbers within", number);
  for (let i = 0; i <= number; i++) {
    if (i % 2 === 0) console.log(i);
  }
}
Enter fullscreen mode Exit fullscreen mode

A function definition begins with the function keyword, followed by the function name getEvenNumber, and then round brackets ( ). Inside these round brackets, we declare parameters such as number. Finally, the function definition ends with curly brackets { }, containing the actual computation code or function body.

getEvenNumber(5);
getEvenNumber(14);
getEvenNumber(9);
Enter fullscreen mode Exit fullscreen mode

To invoke the function, we use its function name getEvenNumber() and provide values as arguments. This triggers the execution of the function, utilizing the provided arguments within the function's computation code.

Parameters and Arguments

Parameters serve as the inputs required by a function to perform its task. When a function is called, it expects certain inputs to work with. These inputs are called arguments. The function utilizes these arguments to execute its code and produce a result.

It's essential to understand that parameters act as placeholders within the function's definition. They are not the actual values but rather placeholders waiting to be filled by the arguments passed during the function call. Therefore, any modifications made to the parameters inside the function do not affect the original values of the arguments.

let name = "John";

function greetPerson(name) {
  name = "Jake"; // Modifying the parameter 'name'
  console.log(`Hello ${name}`); // Output: Hello Jake
}

greetPerson(name); // Calling the function with the argument 'name'
console.log(name); // Output: John (the original value of 'name' remains unchanged)
Enter fullscreen mode Exit fullscreen mode

In the example above, the function greetPerson() accepts a parameter named name. When the function is called with the argument name, the value "John" is copied into the parameter name within the function's scope. However, when we modify the name inside the function to "Jake", it only affects the local copy of the name within the function, not the original variable name defined outside the function. Therefore, when we print name outside the function, it still outputs "John".

Rest and Default Parameters

In JavaScript, we use three dots (...) as prefixes inside function parameters to convert indefinite user-supplied arguments into an array. We can then loop through the array to get access to other parameters. It is an ES6 feature.

function getCoderData(name, language, ...otherInfo) {
  console.log(`${name} uses ${language} for daily coding`); // Debraj uses JavaScript for daily coding.
  console.log(otherInfo); // ["Frontend","DSA"]
}
getCoderData("Debraj", "JavaScript", "Frontend", "DSA");
Enter fullscreen mode Exit fullscreen mode

NOTE: It's a good practice to utilize the spread operator at the end of a function's parameter list.

In JavaScript, if a function expects parameters but is called without providing arguments for those parameters, we can initialize them with default values. This feature, introduced in ES6, ensures that the function can still operate even if specific arguments are not explicitly passed during the function call. If the user does provide arguments, those values will override the default ones.

function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

greet(); // Output: Hello, Guest!
greet("John"); // Output: Hello, John!
Enter fullscreen mode Exit fullscreen mode

In the example above, the function greet() is defined with a parameter name initialized to the default value "Guest". When called without any arguments, the function prints "Hello, Guest!". However, if an argument is provided during the function call, such as "John", it overrides the default value, resulting in the output "Hello, John!". This feature provides flexibility and ensures that functions can gracefully handle different scenarios without relying solely on user-provided arguments.

Return Value of Functions

Functions in JavaScript can also return computed values to the code that called them. When a function encounters a return statement, it immediately exits, and the value specified in the return statement is sent back to the caller.

function sumOfNumbers(a, b) {
  return a + b;
}

const resultOfSum = sumOfNumbers(89, 20);
console.log(resultOfSum); // Output: 109
Enter fullscreen mode Exit fullscreen mode

In the example above, the sumOfNumbers function takes two parameters a and b, computes their sum, and returns the result using the return statement. The returned value, which is the sum of a and b, is then stored in the variable resultOfSum and later printed to the console.

Alternatively, we can directly log the return value without assigning it to a variable:

console.log(sumOfNumbers(89, 20)); // Output: 109
Enter fullscreen mode Exit fullscreen mode

This approach directly prints the returned value of the sumOfNumbers function to the console without storing it in a variable.

Function Expression

In JavaScript, a function expression allows us to define a function inside an expression, much like assigning a value to a variable. Essentially, a function expression involves assigning a function to a variable.

The key distinction between a function expression and a regular function declaration lies in the absence of a function name in the former. Instead of having a named function, the variable itself serves as a means to invoke the function.

JavaScript treats functions as first-class citizens, meaning they can be handled like any other value. This enables various operations such as assigning functions to variables, passing functions as arguments to other functions, returning functions from other functions, and storing functions in data structures like arrays and objects.

Here's an example of a function expression:

const greet = function(name) {
  console.log(`Hello, ${name}!`);
};

greet("John"); // Output: Hello, John!
Enter fullscreen mode Exit fullscreen mode

In this example, the function is defined anonymously within an expression and assigned to the variable greet. The function can then be invoked using the variable greet, demonstrating the flexibility and versatility of function expressions in JavaScript.

Anonymous Functions

In JavaScript, functions are considered first-class citizens, which means they can be treated like any other value.

An anonymous function is a type of function expression that doesn't have a name. Instead of being defined with a specific identifier, anonymous functions are typically assigned to variables or used as arguments to other functions.

const calculatePower = function (x, y) {
  console.log(x ** y);
};

calculatePower(5, 3); // Output: 125
calculatePower(2, 3); // Output: 8
Enter fullscreen mode Exit fullscreen mode

In the example above, calculatePower is a variable that holds an anonymous function. This function takes two parameters x and y and calculates x raised to the power of y. Since it's an anonymous function, it doesn't have a name like traditional functions do. Instead, the variable calculatePower is used to invoke the function. Anonymous functions are useful for situations where a function is needed temporarily or where it's not necessary to have a named function.

Arrow Function

An arrow function is a concise and convenient way to write anonymous function expressions. Introduced in ES6, arrow functions provide a shorter syntax compared to traditional function expressions.

An arrow function does not use the function keyword and lacks a function name. Instead, it uses an arrow (=>) to denote the function's declaration and body.

const doMath = (val1, val2, type) => {
  let ans;
  switch (type) {
    case "add": {
      ans = val1 + val2;
      break;
    }
    case "sub": {
      ans = val1 - val2;
      break;
    }
    case "mul": {
      ans = val1 * val2;
      break;
    }
    case "div": {
      ans = val1 / val2;
      break;
    }
    case "mod": {
      ans = val1 % val2;
      break;
    }
    default: {
      ans = val1 ** val2;
      break;
    }
  }
  return ans;
};

console.log(doMath(45, 5, "div")); // Output: 9
console.log(doMath(45, 6, "mod")); // Output: 3
Enter fullscreen mode Exit fullscreen mode

Arrow functions can also be written as one-liners for simpler tasks:

const doSum = (a, b) => a + b;
console.log(doSum(4, 5)); // Output: 9

const findEven = (n) => n % 2;
console.log(findEven(5)); // Output: false
Enter fullscreen mode Exit fullscreen mode

In the examples above, doSum and findEven are arrow functions that perform addition and check for even numbers, respectively, using concise one-liner syntax. Arrow functions are particularly useful for writing short, inline functions that enhance code readability and maintainability.

Difference between Function Expression and Function Declaration

Both function expressions and function declarations are ways to define functions in JavaScript, but they behave differently in certain aspects.

Hoisting

Function declarations are hoisted, meaning they are available throughout the scope in which they are declared, even if the function is called before it is defined.

greetUser("Rebeka");
function greetUser(name) {
  console.log("Hello ", name); // Output: Hello Rebeka
}
Enter fullscreen mode Exit fullscreen mode

In contrast, function expressions are not hoisted. Attempting to call a function expression before it is defined will result in an error.

greetCoder("Jay");
const greetCoder = (name) => console.log("hello ", name); // ReferenceError: Cannot access 'greetCoder' before initialization
Enter fullscreen mode Exit fullscreen mode

Callback Functions

Function expressions are commonly used for passing callback functions as arguments to other functions. This is because function expressions allow for the creation of anonymous functions inline.

const arr = [1, 2, 3, 4, 5];

arr.forEach((item) => {
  console.log(item); // Output: 1 2 3 4 5
});
Enter fullscreen mode Exit fullscreen mode

While it's technically possible to use function declarations as callbacks, doing so exposes the function to the global scope, which can lead to unintended behavior.

Anonymous Operations

Function expressions are often preferred for anonymous operations, such as immediately invoked function expressions (IIFE). These operations allow for the creation and execution of functions in a single line, without cluttering the global scope.

(() => console.log("IIFE executing arrow function callback"))(); // Output: IIFE executing arrow function callback

(function () {
  console.log("IIFE execution named anonymous function callback"); // Output: IIFE execution named anonymous function callback
})();
Enter fullscreen mode Exit fullscreen mode

In summary, while both function expressions and function declarations have their use cases, function expressions are often preferred for more advanced JavaScript patterns like callbacks and anonymous operations. They offer more control over scope and are generally more flexible in these scenarios.

Closures

In JavaScript, closures are a powerful and often misunderstood concept. A closure is formed when a function is defined within another function and has access to the outer function's variables and parameters, even after the outer function has finished executing.

Example:

function parent(x) {
  function inner(y) {
    return x + y;
  }
  return inner;
}

const sum = parent(10);
console.log(sum(20)); // Output: 30
Enter fullscreen mode Exit fullscreen mode

In this example, inner is a closure because it's defined within the parent function and has access to the x parameter of parent even after parent has finished executing. When sum is invoked with 20 as an argument, it returns 30, as it remembers the value of x (which is 10) from its closure.

Usage of Closures:

  1. Encapsulation/Module Pattern: Closures are commonly used to create private variables and methods in JavaScript, implementing the Module Pattern. This allows for data hiding and abstraction, keeping certain parts of code private and inaccessible from outside scopes.
   function createCounter() {
     let count = 0;

     return {
       increment: function () {
         count++;
       },
       getCount: function () {
         return count;
       },
     };
   }

   let counter = createCounter();
   counter.increment();
   counter.increment();
   console.log(counter.getCount()); // Output: 2
Enter fullscreen mode Exit fullscreen mode
  1. Currying: Currying is a technique in functional programming where a function with multiple arguments is transformed into a sequence of functions, each taking a single argument. Closures are instrumental in achieving currying in JavaScript.
   function add(x) {
     return function (y) {
       return x + y;
     };
   }

   const add5 = add(5);
   console.log(add5(10)); // Output: 15
Enter fullscreen mode Exit fullscreen mode

In summary, closures in JavaScript allow for powerful patterns like encapsulation and currying, enabling developers to write cleaner and more maintainable code by controlling variable scope and access.

Conclusion

If you found this article helpful on your JavaScript learning journey, I'd greatly appreciate your support! Please consider sharing this article, liking it, and following me on this platform for more insightful content.

Happy coding!

Top comments (4)

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

The key distinction between a function expression and a regular function declaration lies in the absence of a function name in the former.

Unfortunately, this is not correct - a function expression can have a name. In the example below, we store a function with the name add inside a variable called myFunction:

const myFunction = function add(a, b) { return a+b }
console.log(myFunction.name)  // 'add' 
Enter fullscreen mode Exit fullscreen mode

You can only call the function with myFunction(...), but the function's name is 'add'.

The 'function' keyword begins a function expression when it appears in a context that cannot accept statements. Whether it is followed by a name or not is irrelevant.

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

In the example above, calculatePower is a variable that holds an anonymous function.

Unfortunately, this is not correct. The act of assigning an anonymous function to a variable makes the function cease to be anonymous. It will acquire the variable's name. In your example the calculatePower variable will hold a function with the name calculatePower - no longer an anonymous function.

const func = function(a, b) { return a+b }
console.log(func.name) // 'func' - the function has a name - it is no longer anonymous

console.log( (function(a,b) { return a+b }).name ) // empty string - an anonymous function
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jonrandy profile image
Jon Randy 🎖️

A closure is formed when a function is defined within another function and has access to the outer function's variables and parameters, even after the outer function has finished executing.

Unfortunately, this is not correct. ALL functions form closures with their surrounding scope - nesting functions is not required.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Might want to check your 'findEven' function example - there are two mistakes 🔎