A JavaScript Refresher Tutorial: Functions

Introduction

In the first part of this tutorial, we’ve seen functions in JavaScript. However, we just scratched the surface. In this part of the tutorial, we’re going to dive deep into them through introducing different related aspects that without them functions in JavaScript wouldn’t have been that interesting.

Closures

Simply put, by definition, a closure in JavaScript is a mechanism that allows access to an outer function’s scope from an inner function.

Let’s have a look at the following example,

function theUnivers(){
  console.log(`The current population of the world is ${currentEarthPopulation}`)
  var sunVisibleForYou = true;
  function onEarth(){
    if(sunVisibleForYou) console.log("It's day time in my location.");
    else console.log("It's night time in my location.");
  }
  return onEarth; // returns the function without invoking it
}
var currentEarthPopulation = 7.7;
var someoneSomewhereOnEarth = theUnivers(); // The current population of the world is 7.7
someoneSomewhereOnEarth(); // It's day time in my location

The function onEarth() had access to the variable sunVisibleForYou defined in the outer lexical scope (theUnivers function scope). How is this possible? What’s intriguing is that at line 12, the function theUnivers() has finished executing, how comes someoneSomewhereOnEarth() (which is basically the onEarth() function) still remembers the value of sunVisibleForYou?

Good question !

Short answer: because of the concept of closures in JavaScript, the inner function onEarth() encloses variables defined in its outer environment and needed for its execution.

Still confused? No problem. Now, I’d like to walk you through the execution – behind the scences- of this example, and elaborate on other related concepts. Hoping it’s going to make more sense to you at the end.

The JavaScript Engine & Execution Contexts

When the JavaScript engine arrives at line 11 and meets the parentheses (), it creates an execution context for theUnivers() function and pushs it to the call stack. When the function finishes executing, the engine pops the execution context off the stack.

This is basically what happens under the hood when you invoke a function in JavaScript.

The Lexical Environment

The JavaScript engine creates an execution context in two phases. During phase one, it creates what we call a lexical environment for that specific function. During phase two, it executes the instructions within the function and pops it off the call stack.

The lexical environment is a specific data structure that records a mapping of identifiers-variables in the heap memory. In other words, it records variables/functions declarations and their actual values (That might update during run-time).

Now, let’s come back to our example and illustrate with a diagram the state of the execution when the JavaScript engine is at line 11.

Diagram 01

Notice how a lexical environment is created for each execution context and allocated in memory. For example, at this actual time of execution (the invokation of theUnivers() function), the Global LE has three entries, theUnivers identifier which holds theUnivers() function definition, currentEarthPopulation variable holding the value 7.7, and the someoneSomewhereOnEarth identifier which is set to undefined.

The Scope Chain

You might have notice that in Diagram 01, there’s a link between the Global LE and the theUnivers LE. The inner function theUnivers() is lexcially nested (writen) within the global scope. Therefore, it has access to the outer (parent) Global LE, and this is due to the mechanisme of scope chaining.

Because of this principle, line 11 prints “The current population of the world is 7.7“. In fact, the JavaScript engines looked up the variable currentEarthPopulation in theUnivers LE first, then since it can’t find it, it goes one level up looking up in the parent LE.

Generally, if there were more nested levels than in our example, the JavaScript engine would have gone up and up following the chain untill it finds it.

It’s important to know that the look up in the chain is always done from the bottom-up and never the vise-versa.

This explains why you can’t access variables defined (written) within functions from outside.

The Closure and the Garbage Collection

At line 12, the JavaScript is already done with theUnivers() function. As a result, its execution context is going to be removed from the call stack, the return value (the onEarth() function definition) is going to be assigned to the variable someoneSomewhereOnEarth, and its lexical environment is going to be garbage collected – means deleted to free memory.

During this time of the execution, our diagram is going to change a little bit as follow…

Diagram 02

At line 12, the JavaScript engine creates the someoneSomewhereOnEarth() LE. It contains two entries an reference to the outer scope (The Global LE), and a reference to the Closure.

Did I say that theUnivers LE is going to be garbage collected? Actually, I meant almost! We still need the variable sunVisibleForYou to execute someoneSomeWhereOnEarth(). Therefore, a closure gets created for it, and a refrence to the closure is added in its LE as shown in Diagram 02.

This is how our function onEarth() remembers the variable sunVisibleForYou. This is what we call Closures, a special way for functions in JavaScript to remember.

IIFE in JavaScript

IIFE…Don’t get intimidated by that fancy name, it’s an abbreviation of Immediately Invoked Function Expression and the concept behind it is very, very, very simple.

IIFEs are just another type of functions in JavaScript with some special superpowers. In fact, they are normal anonymous function expressions that are self-executing. What do we mean by that? Simply put, they do run as soon as they are defined.

Let’s illustrate by an example,

// IIFE
(function(){
  console.log("I am an IIFE, and I run as soon as the script is loaded");
})();

//Normal functiton expression
function callMeToRun(){
  console.log("I am an normal function expression, and I cannot run, if you don't explicitly invoke me!");
}

No matter how ridiculous you think this example is, it shows the difference there’s between an IIFE and a normal function expression. The example is indeed self-explanatory. If you try this piece of code on your end you’ll have something similar to the following,

I am an IIFE, and I run as soon as the script is loaded

The anonymous function we defined got automatically executed as soon it got defined. Therefore, we got the above output. On the other hand, we’re defining the callMeToRun() but never runs as it has never been invoked.

Now that we’ve seen the what and the how of IIFEs, you might be asking yourself and asking me: why would I use IIFEs in the first place?

Never mind, here are three main use cases,

A Shield to Protect the Scope…

If you’ve ever read the JavaScript refresher tutorial in its first part, I talked about the concept of scope in Javascript. In simple words, It actually refers to the visibility (accessibility, availability) of your variables/functions in your code.

In some cases, you’d like to use some third-party code in your script. If you do, you’ll never know if that external code might contain some parts that are going to collide with your initial code (Same variables names or same functions names). As a result, your code might break or give you some unexpected outputs. Doesn’t make sense yet? Let me explain with an example,

let greetingExp = "Hi";
function foo() {
  console.log(123);
}

console.log(greetingExp);
foo(); // 2022 ==> unexpected output! (if you fix the issue caused by line 12)

// Some third-party code
function foo() {
  console.log(2022);
}
let greetingExp = "Hello"; // Code breaks on here => SyntaxError: Identifier 'greetingExp' has already been declared
console.log(greetingExp);
//######################

In the example above, our global variables and functions are in danger. Our code collides with the third-party code we used in line 13 since it uses the same variable name greetingExp. Thus, the execution breaks there.

Assuming that we made a fix by changing the name of that variable to ourGreetingExp, we’d get another issue at line 7. “2022” is going to be printed on the console, and that’s not what we were expecting! The correct output would have been “123“. However, because the third-party code uses the same function name foo, the behaviour of our function has changed.

Let’s use a shield (IIFE) to defend against enemies!

//Our code wrapped in an IIFE
(function(){
  let greetingExp = "Hi";
  function foo() {
    console.log(123);
  }
  
  console.log(greetingExp); //Hi
  foo(); // 123
})();

// Some third-party code
function foo() {
  console.log(2022);
}
let greetingExp = "Hello"; 
console.log(greetingExp); // Hello
//######################

By wrapping our code in an IIFE, we’re protecting our variables and functions – They become scoped to the anonymous immediately invoked function. Therefore, all issues with the last example are resolved and no more collisions with an external code will happen in the future.

Use Some Code Once and Never Again…

Sometimes, you need to use some code for some initiations. You want to use that code only once and never again. So, to avoid any bad surprises that could happen by using that piece of code accidentally once again, you may consider using IIFEs.

// initiations...
(() => {
  // some initiation code
  const intialPalette = ['red', 'white', 'green'];
  const helloMessage = "Hello there!";
  applyPalette(intialPalette);
  displayWelcomeMsg(helloMessage);  
})();

function applyPalette(palette){
  console.log(palette);
}
function displayWelcomeMsg(msg){
 console.log(msg);
}

In the example above, the initiation code would run only once and never again. We’re using it to run some initiation code.

Mimic OOP Private/Public members…

Even though JavaScript provide you with a way to add private and public methods in a class, you could mimic the same features using functions in JavaScript. You can make use of the special IIFs if you’re not willing to use classes in your code.

Let me illustrate with an example,

const bankAccount = (()=>{
  
  //Private
  let balance = 0;
  function accountHolderName(){
    console.log("Private, I can't tell");
  }

  //Public
  return {
    withdraw(amount){
      balance -= amount;
    },
    deposit(amount){
      balance += amount;
    },
    showBalance(){
      return balance;
    }
  }
  
})();

console.log(bankAccount.showBalance()); // 0
bankAccount.deposit(1500);
console.log(bankAccount.showBalance()); // 1500
bankAccount.withdraw(500);
console.log(bankAccount.showBalance()); // 1000
console.log(bankAccount.balance); // undefined ==> private variable
console.log(bankAccount.accountHolderName); // undefined ==> private method

We’re defining a variable bankAccount which is assigned an IIFE. Since we want to keep certain sensitive information private, the IIFE is going to return an object exposing some public methods which are the only way to mutate the state of the bankAccount. On the other hand, members like balance and accountHolderName are kept private given that they are scoped to the IIFE. Therefore, they are not available from the outside world (See lines 29 and 30).

Higher-Order Functions

In the first chapter of this tutorial, we mentioned that “almost everything in JavaScript is an object.” Actually, functions in JavaScript aren’t excluded. In fact, JavaScript treats them like any other type of data since they are ordinary objects with the only difference being that they can be invoked.

So, functions can be assigned to a variable, passed as an argument, or even returned by another function. How cool is that? That’s why we usually hear them refer to them in JavaScript as first-class citizens.

Now, what about higher-order functions? Simply put, these are functions that accept functions as parameters or/and return a function.

Create Your Own Higher-Order Functions…

Let’s have a look at the following scenario,

Assuming that you started working for a tech company as their JavaScript developer (you might be a Junior Developer?). Then, on day 1, the Lead asked you to create a function that is going to take a number and multiply it by 2. The next day, he asked you the same but this time, a function that is going to take a number and multiply it by 10. On the following day (day 3), another function which takes a number and multiplies it by 100…

Your code would probably look something like that,

// DAY 1: multiply by 2
function multiplyBy2(num){
  return num * 2;
}
// DAY 2: multiply by 10
function multiplyBy10(num){
  return num * 10;
}
// DAY 3: multiply by 100
function multiplyBy100(num){
  return num * 100;
}

Then you were lucky and learnt about higher-order functions (HOFs) in JavaScript – Maybe you read this article 🙂

The day after (day 4), the lead is back again to ask you to create a new function that takes a number and multiplies it by 1000. You being a lazy smart guy/girl, you’ve noticed that you’re repeating yourself over and over again…You’re creating the same function with the same logic over and over again. However, because you learnt about HOFs in JavaScript, you thought that probably it’s time to approach this problem differently. So, you’ve made a big change to your previous code,

// DAY 5: A HOF that returns a function
function multiplierFactory(mul){
  return function(num){
    return mul * num;
  }
}

const multiplyBy2 = multiplierFactory(2);
const multiplyBy10 = multiplierFactory(10);
const multiplyBy100 = multiplierFactory(100);
const multiplyBy1000 = multiplierFactory(1000);

console.log(multiplyBy100(3)); //300
console.log(multiplyBy10(2022)); //20220

You created a HOF multiplierFactory that returns a mulitplyByX function (See lines 8-11) which you’re going to invoke when needed (See lines 13, and 14). In this way, you’re making your code a more concise version by avoiding repeating yourself. Moreover, you’re providing a more reusable solution to this specific problem. If on the following day, they ask you to create a new function that takes a number and multiplies it by “123”, then all you need to do is add one line to your code,

const multiplyBy123 = multiplierFactory(123);

By the way, if you’re wondering how the multiplyByX function remembers X, this is due to the concept of closures in JavaScript we’ve seen a while ago in this post.

Use built-in HOFs…

There are many data types in JavaScript that implement HOFs. Mainly arrays. Methods like reduce, filter, map, forEach…etc are all HOFs methods that accept a function as an argument.

Let’s say that you have an initial array of numbers arr and you want to create a new array newArr containing the intial array’s numbers each multipied by 100. How would you do to achieve this?

One way to solve this problem would be,

const arr = [2,30,11, 7];
const newArr = [];

for (let i = 0; i < arr.length; i++) {
  newArr.push(arr[i]*100);
}

console.log(newArr);//[ 200, 3000, 1100, 700 ]

Or you could simply do the following,

const arr = [2,30,11, 7];

const newArr = arr.map(num => num * 100);

console.log(newArr);//[ 200, 3000, 1100, 700 ]

This way, not only you’re making your code more concise, but you’re also hiding the internal detail (the for loop) which means ensuring a high level of abstraction. All of this wouldn’t be possible without HOFs!

Conclusion

Closures, HOFs, IIFEs are all important concepts that tightly relate to the topic of functions in JavaScript. I think that every good JavaScript developer should be aware of them. As this would help them to excel their programming skills in JavaScript.

I hope that this article had help you to learn something new. And hope that you’re going to use this knowledge in your future projects.

If you have any questions or feedback, please, use the comments section below.

Enjoy 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *