Hero Image

Objects anc Classes

Classes and Objects - Basics

Follow Along with the Demo!
Using typeof
typeof is handy operator that returns the name of the data type that follows it. Try running this code in your console:

typeof "hello" // returns "string"  
typeof true // returns "boolean"  
typeof [1, 2, 3] // returns "object" (Arrays are a type of object)  
typeof function hello() { } // returns "function"  

Creating an Object

Paste this JavaScript code in the console to create the object:

const umbrella = {
  color: "pink",
  isOpen: false,
  open: function() {
    if(umbrella.isOpen === true) {
      return "The umbrella is already opened!";
    } else {
      umbrella.isOpen = true;
      return "Julia opens the umbrella!";
    }
  }
}

Using the Object

Then run this code, one line at a time:

umbrella.isOpen; // returns false
umbrella.open(); // returns 'Julia opens the umbrella!'
umbrella.isOpen; // returns true
We'll go into more detail about to create and use objects soon!

Naming Best Practices

  • Don't use a number as the first character in a property name
  • Don't use quotes around key names unless absolutely necessary
  • Use camelCase when you need a multi-word variable name.

These naming conventions also apply to regular variable names too!

Object literals, methods, and properties

You can define objects using object-literal notation:

const myObj = { 
  color: "orange",
  shape: "sphere",
  type: "food",
  eat: function() { return "yummy" }
};

myObj.eat(); // method
myObj.color; // property

Accessing objects

So now that we know what objects look like, how do we retrieve information from them? In other words: how do we access their values? There are two ways: dot notation and square bracket notation. Consider this bicycle object:

const bicycle = {
  color: 'blue',
  type: 'mountain bike',
  wheels: {
    diameter: 18,
    width: 8
  }
};

Using dot notation, we can access bicycle's color property by writing:

bicycle.color;

// 'blue'

Similarly, we can access the same property using square bracket notation by writing:

bicycle['color'];

// 'blue'

Both expressions are equivalent, and will each return 'blue'.

What about nested objects? To retrieve the value of the width property of the object contained within bicycle's wheels property, you can do the following with dot notation:

bicycle.wheels.width;

// 8

And with square bracket notation:

bicycle['wheels']['width'];

// 8

Classes and Objects - Objects in Depth

Creating objects

Creating Objects
To create a new, blank (i.e., “empty”) object, you can use object literal notation, or the Object() constructor function. If you're not familiar with constructor functions, no need to worry! We'll jump into them in-depth in Lesson 3. For now, just know that the following two expressions are equivalent:

// Using literal notation:

const myObject = {};

// Using the Object() constructor function:

const myObject = new Object();

While both methods ultimately return an object without properties of its own, the Object() constructor function is a bit slower and more verbose. As such, the recommended way to create new objects in JavaScript is to use literal notation.

Modifying Properties

Keep in mind that data within objects are mutable, meaning that data can be changed. There are a few exceptions to this, but for now, let's see how we can modify/reassign existing properties in an object.

Consider the following cat object:

const cat = {
  age: 2,
  name: 'Bailey',
  meow: function () {
    console.log('Meow!');
  },
  greet: function (name) {
    console.log(`Hello ${name}`);
  }
};

Now, let's go ahead change it up a bit!

cat.age += 1;

cat.age;
// 3

cat.name = 'Bambi';

cat.name;
// 'Bambi'
After incrementing the value of the age property by 1, and reassigning name's value to 'Bambi', our cat object now looks like:

{
  age: 3,
  name: 'Bambi',
  meow: function () {
    console.log('Meow!');
  },
  greet: function (name) {
    console.log(`Hello ${name}`);
  }
};

Adding Properties

Properties can be added to objects simply by specifying the property name, then giving it a value. Let's start off with a blank object, then add two properties:

const printer = {};

printer.on = true;
printer.mode = 'black and white';

Removing Properties

Recall that since objects are mutable, not only can we modify existing properties (or even add new ones) -- we can also delete properties from objects.

Say that the printer object above actually doesn't have any modes (i.e., 'black and white', 'color', etc.). We can go ahead and remove that property from printer using the delete operator.

delete printer.mode;

// true

Passing Arguments

Passing a Primitive

In JavaScript, a primitive (e.g., a string, number, boolean, etc.) is immutable. In other words, any changes made to an argument inside a function effectively creates a copy local to that function, and does not affect the primitive outside of that function. Check out the following example:

function changeToEight(n) {
  n = 8; // whatever n was, it is now 8... but only in this function!
}

let n = 7;

changeToEight(n);

console.log(n);
// 7

changeToEight() takes in a single argument, n, and changes it to 8. However, this change only exists inside the function itself. We then pass the global variable n (which is assigned the value 7) into the function. After invoking it, n is still equal to 7.

Passing an Object

On the other hand, objects in JavaScript are mutable. If you pass an object into a function, Javascript passes a reference to that object. Let's see what happens if we pass an object into a function and then modify a property:

let originalObject = {
  favoriteColor: 'red'
};

function setToBlue(object) {
  object.favoriteColor = 'blue';
}

setToBlue(originalObject);

originalObject.favoriteColor;
// 'blue'

In the above example, originalObject contains a single property, favoriteColor, which has a value of 'red'. We pass originalObject into the setToBlue() function and invoke it. After accessing originalObject's favoriteColor property, we see that the value is now 'blue'!

How did this happen? Well, since objects in JavaScript are passed by reference, if we make changes to that reference, we're actually directly modifying the original object itself!

What's more: the same rule applies when re-assigning an object to a new variable, and then changing that copy. Again, since objects are passed by reference, the original object is changed as well. Let's take a look at this more closely with another example.

Consider this iceCreamOriginal object, which shows the amount of ice cream cones each instructor has eaten:

const iceCreamOriginal = {
  Andrew: 3,
  Richard: 15
};

Let's go ahead and make assign a new variable to iceCreamOriginal. We'll then check the value of its Richard property:

const iceCreamCopy = iceCreamOriginal;

iceCreamCopy.Richard;
// 15

As expected, the expression iceCreamCopy.Richard; returns 15 (i.e., it is the same value as the Richard property in iceCreamOriginal). Now, let's change the value in the copy, then check the results:

iceCreamCopy.Richard = 99;

iceCreamCopy.Richard;
// 99

iceCreamOriginal.Richard;
// 99

Since objects are passed by reference, making changes to the copy (iceCreamCopy) has a direct effect on the original object (iceCreamOriginal) as well. In both objects, the value of the Richard property is now 99.

Attention: copies of objects end up in references, not local copies!!!

Comparing an Object with Another Object

On the topic of references, let's see what happens when we compare one object with another object. The following objects, parrot and pigeon, have the same methods and properties:

const parrot = {
  group: 'bird',
  feathers: true,
  chirp: function () {
    console.log('Chirp chirp!');
  }
};

const pigeon = {
  group: 'bird',
  feathers: true,
  chirp: function () {
    console.log('Chirp chirp!');
  }
};

Naturally, one might expect the parrot object and pigeon object to be equal. After all, both objects look exactly the same! Let's compare parrot and pigeon to find out:

parrot === pigeon;

// false

What's going on here? As it turns out, the expression will only return true when comparing two references to exactly the same object. Using what we now know about passing objects, let's confirm this. To start off, let's create a new variable, myBird, and assign it to one of the objects above:

const myBird = parrot;

As we've just learned, myBird not only refers to the same object as parrot -- they are the same object! If we make any updates to myBird's properties, parrot's properties will be updated with exactly the same changes as well. Now, the comparison will return true:

myBird === parrot;

// true

Summary

Objects are commonly created with literal notation, and can include properties that point to functions called methods. Methods are accessed the same way as other properties of objects, and can be invoked the same way as regular functions, except they automatically have access to the other properties of their parent object.

By default, objects are mutable (with a few exceptions), so data within them can be altered. New properties can be added, and existing properties can be modified by simply specifying the property name and assigning (or re-assigning) a value. Additionally, properties and methods of an object can be deleted as well with the delete operator, which directly mutates the object.

We've modified objects quite a bit in this section, and even added new methods into them. In the very next section, we'll take a closer look at invoking these methods, as well as how these methods can directly access and modify an object itself!

Important

Primitives types (strings, numbers, boolean, etc. ) are immutable which means a local copy is created within a function

Objects, Arrays and Functions are mutable and only references will applied to functions!!!

Classes and Objects - Functions at Runtime

Invoking Object Methods

Functions vs. Methods

At this point, we've mostly seen objects with properties that behave more like attributes. That is, properties such as color or type are data that describe an object, but they don't "do" anything. We can extend functionality to objects by adding methods to them.

Say that we have a function, sayHello(), which simply logs a message to the console:

function sayHello () {
  console.log('Hi there!');
}

Now, say that we also have a developer object with a single property, name:

const developer = {
  name: 'Andrew'
};

If we want to add the sayHello() function into the developer object, we can add the same way as we add other new properties: by providing property name, then giving it a value. This time, the value of the property is a function!

developer.sayHello = function () {
  console.log('Hi there!');
};

This is how the updated developer object looks:

{
  name: 'Andrew',
  sayHello: function () {
    console.log('Hi there!');
  }
}

So now that a sayHello property has been defined, how do we go about calling (i.e., invoking) its referenced function?

Calling Methods

We can access a function in an object using the property name. Again, another name for a function property of an object is a method. We can access it the same way that we do with other properties: by using dot notation or square bracket notation. Let's take a look back at the updated developer object above, then invoke its sayHello() method:

const developer = {
  name: 'Andrew',
  sayHello: function () {
    console.log('Hi there!');
  }
};

Passing Arguments Into Methods

If the method takes arguments, you can proceed the same way, too:

const developer = {
  name: 'Andrew',
  sayHello: function () {
    console.log('Hi there!');
  },
  favoriteLanguage: function (language) {
    console.log(`My favorite programming language is ${language}`);
  }
};

developer.favoriteLanguage('JavaScript');
// My favorite programming language is JavaScript'

Examples

Write an expression that invokes the alerter() function in the following array, myArray:

const myArray = [ function alerter() { alert('Hello!'); } ];
myArray[0]()

A Method Can Access the Object it was Called On

Recall that an object can contain data and the means to manipulate that data. But just how can an object reference its own properties, much less manipulate some of those properties itself? This is all possible with the this keyword!

Using this, methods can directly access the object that it is called on. Consider the following object, triangle:

const triangle = {
  type: 'scalene',
  identify: function () {
    console.log(`This is a ${this.type} triangle.`);
  }
};

Note that inside the identify() method, the value this is used. When you say this, what you're really saying is "this object" or "the object at hand." this is what gives the identify() method direct access to the triangle object's properties:

triangle.identify();

// 'This is a scalene triangle.'

When the identify() method is called, the value of this is set to the object it was called on: triangle. As a result, the identify() method can access and use triangle's type property, as seen in the above console.log() expression.

Note that this is a reserved word in JavaScript, and cannot be used as an identifier (e.g. variable names, function names, etc.).

💡 The value of this💡

Depending on how a function is called, this can be set to different values! Later in this course, we'll take a deep dive into different ways that functions can be invoked, and how each approach influences the value of this.

Summary

A method is a function property of an object. It is accessed the same way as any other property of the object (i.e., using dot notation or square bracket notation), and is invoked the same way as a regular function outside of an object (i.e., adding parentheses to the end of the expression).

Since an object is a collection of data and the means to operate on that data, a method can access the object it was called on using the special this keyword. The value of this is determined when a method is invoked, and its value is the object on which the method was called. Since this is a reserved word in JavaScript, its value cannot be used as an identifier. Feel free to check out the links below for an additional look at methods and their relationship with this.

We've spent a bit of time on this inside objects, but did you know that the value of this can have different meanings outside an object? In the next section, we'll take a close look at globals, their relationship with this, and the implications of using them.

Beware of Globals

What is this?

Now, let's check out a different example. What do you think will be the value of this inside the following code?

function whoThis () {
  this.trickyish = true
}

whoThis();
// (what does the above expression output?)

The this-object within a global functions gives then the parent object back. In our case the window obejct.

The window Object

If you haven't worked with the window object yet, this object is provided by the browser environment and is globally accessible to your JavaScript code using the identifier, window. This object is not part of the JavaScript specification (i.e., ECMAScript); instead, it is developed by the W3C.

This window object has access to a ton of information about the page itself, including:

  • The page's URL (window.location;)
  • The vertical scroll position of the page (window.scrollY')
  • Scrolling to a new location (window.scroll(0, window.scrollY + 200); to scroll 200 pixels down from the current location)
  • Opening a new web page (window.open("https://www.udacity.com/");)

Global Variables are Properties on window

Since the window object is at the highest (i.e., global) level, an interesting thing happens with global variable declarations. Every variable declaration that is made at the global level (outside of a function) automatically becomes a property on the window object!

Here we can see that the currentlyEating variable is set to 'ice cream'. Then, we immediately see that the window now has a currentlyEating property! Checking this property against the currentlyEating variable shows us that they are identical.

var currentlyEating = 'ice cream';

window.currentlyEating === currentlyEating

Recap: Globals and var, let, and const

The keywords var, let, and const are used to declare variables in JavaScript. var has been around since the beginning of the language, while let and const are significantly newer additions (added in ES6).

Only declaring variables with the var keyword will add them to the window object. If you declare a variable outside of a function with either let or const, it will not be added as a property to the window object.

let currentlyEating = 'ice cream';

window.currentlyEating === currentlyEating 
// false!

Global Functions are Methods on window

Similarly to how global variables are accessible as properties on the window object, any global function declarations are accessible on the window object as methods:

function learnSomethingNew() {
  window.open('https://www.udacity.com/');
}

window.learnSomethingNew === learnSomethingNew
// true

Declaring the learnSomethingNew() function as a global function declaration (i.e., it's globally accessible and not written inside another function) makes it accessible to your code as either learnSomethingNew() or window.learnSomethingNew().

Summary

The window object is provided by the browser and is not part of the JavaScript language or specification. Any global variable declarations (i.e., those that use var) or global function declarations are added as properties to this window object. Excessive use of global variables is not a good practice, and can cause unexpected problems to accurately-written code.

Whether you're working with the window object, or with an object you create yourself, recall that all objects are made up of key/value pairs. In the next section, we'll check out how to extract these individual keys or values!

Extracting Properties and Values

Object Methods

Do you remember earlier when we used the Object() constructor function to create (i.e., instantiate) new objects with the new keyword?

const myNewFancyObject = new Object();

The Object() function actually includes a few methods of its own to aid in the development of your applications. These methods are:

  • Object.keys()
  • Object.values()

Whether you're building logic in your code, or just writing a utility "helper" function, feel free to use these methods as necessary. Let's see how each of these work!

Object.keys() and Object.values()

At its core, an object is just a collection of key/value pairs. What if we want to extract only the keys from an object? Say we have this object representing a real-life dictionary:

const dictionary = {
  car: 'automobile',
  apple: 'healthy snack',
  cat: 'cute furry animal',
  dog: 'best friend'
};

Having a collection of just the words (i.e., the dictionary object's keys) may be particularly useful. While we could use a for...in loop to iterate through an object and build our own list of keys, it can get a bit messy and verbose. Thankfully, JavaScript provides an abstraction just for this!

When Object.keys() is given an object, it extracts just the keys of that object, then returns those keys in an array:

Object.keys(dictionary);

// ['car', 'apple', 'cat', 'dog']

So Object.keys() gives returns an array of the provided object's property names. Likewise, if we want a list of the values of an object, we can use Object.values():

Object.values(dictionary);

// ['automobile', 'healthy snack', 'cute furry animal', 'best friend']

Summary

The Object() constructor function has access to several methods to aid in development. To extract property names and values from an object, we can use:

  • Object.keys() returns an array of a given object's own keys (property names).
  • Object.values() returns an array of a given object's own values (property values).

Functions at Runtime

Functions are First-Class Functions

In JavaScript, functions are first-class functions. This means that you can do with a function just about anything that you can do with other elements, such as numbers, strings, objects, arrays, etc. JavaScript functions can:

  • Be stored in variables
  • Be returned from a function.
  • Be passed as arguments into another function.
    Note that while we can, say, treat a function as an object, a key difference between a function and an object is that functions can be called (i.e., invoked with ()), while regular objects cannot.

Functions Can Return Functions

Recall that a function must always return a value. Whether the value is explicitly specified in a return statement (e.g., returning a string, boolean, array, etc.), or the function implicitly returns undefined (e.g., a function that simply logs something to the console), a function will always return just one value.

Since we know that functions are first-class functions, we can treat a function as a value and just as easily return a function from another function! A function that returns another function is known as higher-order function. Consider this example:

function alertThenReturn() {
  alert('Message 1!');

  return function () {
    alert('Message 2!');
  };
}

If alertThenReturn() is invoked in a browser, we'll first see an alert message that says 'Message 1!', followed by the alertThenReturn() function returning an anonymous function. However, we don't actually see an alert that says 'Message 2!', since none of the code from the inner function is executed. How do we go about executing the returned function?

Since alertThenReturn() returns that inner function, we can assign a variable to that return value:

const innerFunction = alertThenReturn();
We can then use the innerFunction variable like any other function!

innerFunction();

// alerts 'Message 2!'

Likewise, this function can be invoked immediately without being stored in a variable. We'll still get the same outcome if we simply add another set of parentheses to the expression alertThenReturn();:

alertThenReturn()();

// alerts 'Message 1!' then alerts 'Message 2!'

Notice the double set of parentheses (i.e. ()()) in that function call! The first pair of parentheses executes the alertThenReturn() function. The return value of this invocation is a function, which then gets invoked by the second pair of parentheses!

Callbacks - Functions passed as an arguement

Recall that JavaScript functions are first-class functions. We can do with functions just about everything we can do with other values -- including passing them into other functions! A function that takes other functions as arguments (and/or returns a function, as we learned in the previous section) is known as a higher-order function. A function that is passed as an argument into another function is called a callback function.

We'll be focusing on callbacks in this section. Callback functions are great because they can delegate calling functions to other functions. They allow you to build your applications with composition, leading to cleaner and more efficient code.

Simple example:

function callAndAdd(n, callbackFunction) {
  return n + callbackFunction();
}

function returnsThree() {
  return 3;
}

let result = callAndAdd(2, returnsThree);

console.log(result);
Array Methods

Where have you probably seen callback functions used? In array methods! Functions are commonly passed into array methods and called on elements within an array (i.e., the array on which the method was called).

Let's check out a couple in detail:

  • forEach()
  • map()
  • filter()
forEach()

Array's forEach() method takes in a callback function and invokes that function for each element in the array. In other words, forEach() allows you to iterate (i.e., loop) through an array, similar to using a for loop. Check out its signature:

array.forEach(function callback(currentValue, index, array) {
    // function code here
});

The callback function itself receives the arguments: the current array element, its index, and the entire array itself.

Let's say we have a simple function, logIfOdd(), that takes in a single number and logs it to the console if that number is an odd number:

function logIfOdd(n) {
  if (n % 2 !== 0) {
    console.log(n);
  }
}

logIfOdd(2);
// (nothing is logged)

logIfOdd(3);
// 3

Example of using:

[1, 5, 2, 4, 6, 3].forEach(function logIfOdd(n) {
  if (n % 2 !== 0) {
    console.log(n);
  }
});
map()

Array's map() method is similar to forEach() in that it invokes a callback function for each element in an array. However, map() returns a new array based on what's returned from the callback function. Check out the following:

const names = ['David', 'Richard', 'Veronika'];

const nameLengths = names.map(function(name) {
  return name.length;
});
filter()

Array's filter() method is similar to the map() method:

  • It is called on an array
  • It takes a function as an argument
  • It returns a new array

The difference is that the function passed to filter() is used as a test, and only items in the array that pass the test are included in the new array. Consider the following example:

const names = ['David', 'Richard', 'Veronika'];

const shortNames = names.filter(function(name) {
  return name.length < 6;
});
Just as before, let's break it down a bit! We have the starting array:

const names = ['David', 'Richard', 'Veronika'];
We call filter() on the names array and pass it a function as an argument:

names.filter(function(name) {
  return name.length < 6;
});

Scope

A function's runtime scope describes the variables available for use inside a given function. The code inside a function has access to:

  1. The function's arguments.
  2. Local variables declared within the function.
  3. Variables from its parent function's scope.
  4. Global variables.

Check out the following image that highlights a function's scope, then we'll take a look at a live example.

Scope Chain

Whenever your code attempts to access a variable during a function call, the JavaScript interpreter will always start off by looking within its own local variables. If the variable isn't found, the search will continue looking up what is called the scope chain. Let's take a look at an example:

function one() {
  two();
  function two() {
    three();
    function three() {
      // function three's code here
    }
  }
}
one();

In the above example, when one() is called, all the other nested functions will be called as well (all the way to three()).

You can visualize the scope chain moving outwards starting at the innermost level: from three(), to two(), to one(), and finally to window (i.e., the global/window object). This way, the function three() will not only have access to the variables and functions "above" it (i.e., those of two() and one()) -- three() will also have access to any global variables defined outside one().

Let's now revisit the image from the beginning of this section, and visualize the entire process:

Overview scope of variables

💡 The Global (window) Object💡
Recall that when JavaScript applications run inside a host environment (e.g., a browser), the host provides a window object, otherwise known as the global object. Any global variables declared are accessed as properties of this object, which represents the outermost level of the scope chain.

Summary

When a function is run, it creates its own scope. A function's scope is the set of variables available for use within that function. The scope of a function includes:

  1. The function's arguments.
  2. Local variables declared within the function.
  3. Variables from its parent function's scope.
  4. Global variables.

Variables defined with the var keyword in JavaScript are also function-scoped. This means that any such variables defined inside a function are not available for use outside the function, though any variables defined within blocks (e.g. if or for) are available outside that block.

Variables defined with const and let keywords - are block-scoped, meaning that any variables defined within blocks are not available outside the block. When it comes to accessing variables, the JavaScript engine will traverse the scope chain, first looking at the innermost level (e.g., a function's local variables), then to outer scopes, eventually reaching the global scope if necessary.

In this section, we've seen quite a few examples of a nested function being able to access variables declared in its parent function's scope (i.e., in the scope in which that function was nested). These functions, combined with the lexical environment it which it was declared, actually have a very particular name: closure. Closures are very closely related to scope in JavaScript, and lead to some powerful and useful applications. We'll take a look at closures in detail next!

Closure

Creating a Closure

Every time a function is defined, closure is created for that function. Strictly speaking, then, every function has closure! This is because functions close over at least one other context along the scope chain: the global scope. However, the capabilities of closures really shine when working with a nested function (i.e., a function defined within another function).

Recall that a nested function has access to variables outside of it. From what we have learned about the scope chain, this includes the variables from the outer, enclosing function itself (i.e., the parent function)! These nested functions close over (i.e., capture) variables that aren't passed in as arguments nor defined locally, otherwise known as free variables.

As we saw with the remember() function earlier, it is important to note that a function maintains a reference to its parent's scope. If the reference to the function is still accessible, the scope persists!

const number = 3;

function myFunction () {
  const otherNumber = 1;

  function logger() {
    console.log(otherNumber);
  }

  return logger;
}

const result = myFunction();

result();
// 1

Intersesting point here is that automaticly a closure object is created where the otherNumber variable is saved...

Closure

Summary

To recap, we've seen two common and powerful applications of closures:

  1. Passing arguments implicitly.
  2. At function declaration, storing a snapshot of scope.
/*

Declare a function named `expandArray()` that:

* Takes no arguments
* Contains a single local variable, `myArray`, which points to [1, 1, 1]
* Returns an anonymous function that directly modifies `myArray` by
  appending another `1` into it
* The returned function then returns the value of `myArray`

*/
function expandArray()
{
    const myArray = [1,1,1];
    return function () {
        myArray.push(1);
        return myArray;
    }
}

const result = expandArray();

result();

Garbage Collection

JavaScript manages memory with automatic garbage collection. This means that when data is no longer referable (i.e., there are no remaining references to that data available for executable code), it is "garbage collected" and will be destroyed at some later point in time. This frees up the resources (i.e., computer memory) that the data had once consumed, making those resources available for re-use.

Let's look at garbage collection in the context of closures. We know that the variables of a parent function are accessible to the nested, inner function. If the nested function captures and uses its parent's variables (or variables along the scope chain, such as its parent's parent's variables), those variables will stay in memory as long as the functions that utilize them can still be referenced.

As such, referenceable variables in JavaScript are not garbage collected! Let's quickly look back at the myCounter function from the previous video:

function myCounter() {
  let count = 0;

  return function () {
    count += 1;
    return count;
  };
}

The existence of the nested function keeps the count variable from being available for garbage collection, therefore count remains available for future access. After all, a given function (and its scope) does not end when the function is returned. Remember that functions in JavaScript retain access to the scope that they were created in!

Summary

A closure refers to the combination of a function and the lexical environment in which that function was declared. Every time a function is defined, closure is created for that function. This is especially powerful in situations where a function is defined within another function, allowing the nested function to access variables outside of it. Functions also keep a link to its parent's scope even if the parent has returned. This prevents data in its parents from being garbage collected.

In JavaScript, closures are created every time a function is created, at function creation time. A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function 1.

Closures are often used to create private variables and methods in JavaScript. A private variable is a variable that is only accessible within the function or object that it is defined in. By using closures, you can create functions that have access to private variables and methods, which can be useful for encapsulating functionality and preventing naming collisions.

Immediately-Invoked Function Expressions (IIFE)

Function Declarations vs. Function Expressions

Before we jump into immediately-invoked function expressions (IIFE), let's make sure we're on the same page regarding the differences between function declarations and function expressions.

A function declaration defines a function and does not require a variable to be assigned to it. It simply declares a function, and doesn't itself return a value. Here's an example:

function returnHello() {
  return 'Hello!';
}

On the other hand, a function expression does return a value. Function expressions can be anonymous or named, and are part of another expression's syntax. They're commonly assigned to variables, as well. Here's the same function as a function expression:

// anonymous
const myFunction = function () {
  return 'Hello!';
};

// named
const myFunction = function returnHello() {
  return 'Hello!';
};

Passing Arguments into IIFE's

Let's look into how we can go about passing arguments into IIFE's. Consider the following example of an anonymous function expression that takes in a single argument:

(function (name){
    alert(`Hi, ${name}`);
  }
)('Andrew');

// alerts 'Hi, Andrew'

IIFE's and Private Scope

One of the primary uses for IIFE's is to create private scope (i.e., private state). Recall that variables in JavaScript are traditionally scoped to a function. Knowing this, we can leverage the behavior of closures to protect variables or methods from being accessed! Consider the following example of a simple closure within an IIFE, referenced by myFunction:

const myFunction = (
  function () {
    const hi = 'Hi!';
    return function () {
      console.log(hi);
    }
  }
)();

Let's break myFunction down and review the individual parts that make it up:

IFFE

In the above image, an immediately-invoked function expression is used to immediately run a function. This function runs and returns an anonymous function that is stored in the myFunction variable.

Note that the function that is being returned closes over (i.e., captures) the hi variable. This allows myFunction to maintain a private, mutable state that cannot be accessed outside the function! What's more: because the function expressed is called immediately, the IIFE wraps up the code nicely so that we don't pollute the global scope.

If any of this sounds familiar -- it's because IIFE's are very closely related to everything you've learned about scope and closures!

💡 Alternative Syntax for IIFE's 💡
Recall the example from the beginning of this section:

(function sayHi(){
   alert('Hi there!');
 }
)();

// alerts 'Hi there!'
This works great, but there's also another way we can write this to achieve the same results! The first set of parentheses can wrap around the entire expression. That is, we can move the first closing parenthesis to the very end:

(function sayHi(){
   alert('Hi there!');
}());

// alerts 'Hi there!'
Again, using either approach will still produce the same result: alerting 'Hi there!' in the browser.

Example for IIFE's, Private Scope, and Event Handling

Let's check out another example of an immediately-invoked function expression -- this time in the context of handling an event. Say that we want to create a button on a page that alerts the user on every other click. One way to begin doing this would be to keep track of the number of times that the button was clicked. But how should we maintain this data?

We could keep track of the count with a variable that we declare in the global scope (this would make sense if other parts of the application need access to the count data). However, an even better approach would be to enclose this data in event handler itself!

For one, this approach prevents us from polluting the global with extra variables (and potentially variable name collisions). What's more: if we use an IIFE, we can leverage a closure to protect the count variable from being accessed externally! This prevents any accidental mutations or unwanted side-effects from inadvertently altering the count.

To begin, let's first create an HTML file containing a single button:

<!-- button.html -->

<html>

  <body>

     <button id='button'>Click me!</button>

     <script src='button.js'></script>

  </body>

</html>

No surprises here -- just a button tag with ID of 'button'. We also reference a button.js file that we're now going to build. Within that file, let's retrieve a reference to that element via its ID, then save that reference to a variable, button:

// button.js

const button = document.getElementById('button');

Next, we'll add an event listener to button, and listen for a 'click' event. Then, we'll pass in an IIFE as the second argument:

// button.js

// the callback is implemented as IIFE
button.addEventListener('click', (function() {
  // due to IIFE the count variable is handeled as private scope (only within the callback-function. 
  let count = 0;

  return function() {
    // this count closes up to the count variable and keeps therefore the value

    count += 1;
    if (count === 2) {
      alert('This alert appears every other press!');
      count = 0;
    }
  };
})());

Quite a bit is going on in the IIFE, so let's break it down!

First, we declare a local variable, count, which is initially set to 0. We then return a function from that function. The returned function increments count, but alerts the user and resets the count back to 0 if the count reaches 2.

What is important to note is that the returned function closes over the count variable. That is, because a function maintains a reference to its parent's scope, count is available for the returned function to use! As a result, we immediately invoke a function that returns that function. And since the returned function has access to the internal variable, count, a private scope is created -- effectively protecting the data!

Containing count in a closure allows us to retain the data from each click. Now, let's see this all in action!

Benefits of Immediately-Invoked Function Expressions

We've seen how using an immediately-invoked function expression creates a private scope that protects variables or methods from being accessed. IIFE's ultimately use the returned functions to access private data within the closure. This works out very well: while these returned functions are publicly-accessible, they still maintain privacy for the variables defined within them!

Another great opportunity to use an IFFE is when you want to execute some code without creating extra global variables. However, note that an IIFE is only intended to be invoked once, to create a unique execution context. If you have some code that is expected to be re-used (e.g., a function meant to be executed more than once in the application), declaring the function and then invoking it might be a better option.

All in all, if you simply have a one-time task (e.g., initializing an application), an IIFE is a great way to get something done without polluting your the global environment with extra variables. Cleaning up the global namespace decreases the chance of collisions with duplicate variable names, after all.

Summary

An immediately-invoked function expression (IIFE) is a function that is called immediately after it is defined. Utilizing an IIFE alongside closures allows for a private scope, which maintains privacy for variables defined within them. And since less variables are created, an IIFE will help to minimize pollution of the global environment, hindering the chances of variable name collisions.