JavaScript in the Browser

7.2 Code Structure

# Code Structure

In addition to organizing your code across your project, it is also important that you understand some of the terminology and practices that describe how you can structure your code within you modules and namespaces.

# Callback Functions

A callback function is just a regular function, like any function that you have written so far. What makes a function into a callback function is simply how you use it.

When you pass a function reference to a second function so that it can be called, from inside the second function, after the rest of the second function code is complete, then the function reference being passed in is called a callback function.

function myCallback() {
  //this function will be called by another function as a callback
  console.log('This is the callback');
}

function countToTen(cb) {
  //cb is a variable that will hold the callback function reference
  for (let i = 1; i <= 10; i++) {
    console.log(i);
  }
  cb(); //make the callback function run now
}

countToTen(myCallback);
//call the countToTen function
//pass in the reference to the myCallback function
//note there are no parentheses after `myCallback`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

This video explains what callback functions are:

This video is a sample interview question, which will let you check if you understand what a callback is and how it works. It also provides the solution.

# Higher Order Functions

Functions in JavaScript are called first-class objects because they can be passed around just like any other variable. We can pass a function reference to another function, like we do for callback functions. We can also return a function from a function.

function f1() {
  console.log('this is function f1');
  return function () {
    console.log('this is an anonymous function');
    //this function will be returned from f1
    return 42;
  };
}

const f2 = f1();
//call the function f1 and put it's return value into f2.
//f2 is now a reference to the anonymous function returned by f1

let num = f2(); // runs the anonymous function returned by f1
console.log(num); // outputs 42, which is the return value of the anonymous function in f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Recursive Functions

When a function calls itself, this is known as an recursive function.

function it() {
  it(); //iteratively call the function `it`
}

it(); //start the infinite iterative looping
1
2
3
4
5

While, technically this is an iterative function call, it does have one huge problem - it never stops calling itself.

When you iteratively call a function you need to have a stop condition.

Let's do the same thing but say that we only want to call the function 10 times.

function it(count = 0) {
  //if the function is called without a `count` argument then `count` will be set to zero.
  count++;
  //increment the value of `count`
  if (count <= 10) {
    it(count); //iteratively call the function `it` with the current value of `count`
    //but only if count is less than or equal to ten
  }
}

it(1); //call `it` with an initial value of 1
1
2
3
4
5
6
7
8
9
10
11

# Closures

Closures are not something that you learn how to write in JavaScript, they are a feature of the language. Closures are something that you need to be aware of and understand how they impact the scope of variables.

When you call a function, an execution context gets created with its own scope. Inside this context you can declare variables or create other functions. When creating the other functions they will understand what their execution context is (where they were created). If you declared variables in the same execution context as where the function is being created, then the function will be aware of those variables too.

function f1() {
  let name = 'Jon';
  return function () {
    console.log(name);
  };
}

const f2 = f1();
//call `f1`, which will create the variable `name` and the anonymous function
//the anonymous function is put into `f2`.
f2();
//call `f2`.
//`f2` needs a variable called `name`.
//There is no variable called `name` declared or assigned a value inside of `f2`
//JavaScript will look inside the execution context where the anonymous function was created
//inside that execution context there WAS a variable called `name`. So, it gets used
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

So, when function A runs and it returns function B, the fact that B has access to anything that was declared inside the execution context of A is called a closure. We are creating a bubble around all the potential variables inside that execution context to prevent them being deleted by the garbage collection process.

Normally, when you run a function and that function finishes running, all the locally declared variables are no longer needed. JavaScript is allowed to delete them and free up the memory.

A closure will prevent the garbage collection as long as the returned function exists / is referenced.

# Currying

When you intentionally write a function which returns a second function, and the second function references variables that are inside its own scope plus variables that came from its execution context, this is known as currying.

Currying is a great way to dynamically create a series of functions that are slight variations of the same functionality, without having to actually write all the function variations.

Let's say that we want to create a series of message functions. We have messages that are informative, ones that indicate success, and ones that indicate failure. Each of those three will have a different css style, need to include the name of the current user, and will not know the message text until later on when the user is interacting with the web app.

const message = function (username, type) {
  let div = document.createElement('div');
  let h2 = document.createElement('h2');
  h2.textContent = `Attention: ${username}`;
  let p = document.createElement('p');
  p.className = `message ${type}`;
  div.append(h2, p);
  //we have created a message box that can be used later by the returned function below
  return function (msg) {
    p.textContent = msg; //add the message to the div > p
    document.body.append(div); //add the div to the body
    //this part of the function runs later on.
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

So, message is a function that we can use to create a message box. The user's name will be contained in the heading for the message box. The actual message gets added later when the message box will be added to the webpage.

With the code from above we can now do the currying and create the function variations.

let user = 'Sam';
const info = message(user, 'infoMsg');
const success = message(user, 'successMsg');
const fail = message(user, 'errorMsg');
1
2
3
4

We now have three functions info, success, and fail, which can all be called at any point later when we need one of them.

success('Congrats! It worked');

info('You are currently logged in');

fail('Your credit card was declined');
1
2
3
4
5

All three will use the div that we created originally that contains the CSS classname and the username to display a message to the user. The message to the user was only created when one of those three functions was actually called.

# This and Context

The keyword this can be a confusing one in JavaScript. It is typically a reference to the object that made a function run.

For the purposes of this discussion we will limit the use of this to functions triggered by event listeners.

let btn1 = document.getElementById('myButton');
let btn2 = document.querySelector('.btn');
let btn3 = document.querySelector('#otherButton');

btn1.addEventListener('click', makeNoisy);
btn2.addEventListener('click', makeNoisy);
btn3.addEventListener('click', makeNoisy);

function makeNoisy(ev) {
  ev.currentTarget.style.backgroundImage = 'url(./img/noisy-pattern.png)';
}
1
2
3
4
5
6
7
8
9
10
11

In this example we have three different buttons being referenced in the variables btn1, btn2, and btn3.

All three buttons have a click listener.

All three click listeners will call the function makeNoisy( ).

Inside the function makeNoisy, we are able to tell which button needs to have the background image added because ev.currentTarget gives us that information. The target property points to the button that was clicked.

The keyword this works in the same way.

function makeNoisy(ev) {
  this.style.backgroundImage = 'url(.img/noisy-pattern.png)';
}
1
2
3

We can replace ev.currentTarget with this.

This points to the object written in front of addEventListener( ).

To learn a lot more about the keyword this watch the following video:

# This - Beyond Events

This excerpt from the You Don't Know JS book by Kyle Simpson gives a good description of the 4 rules for determining the value of this. Here is the link to the page in You Don't Know JS (opens new window)


We can summarize the rules for determining this from a function call's call-site,in their order of precedence. Ask these questions in this order, and stop when the first rule applies.

  1. Is the function called with new (new binding)? If so, this is the newly constructed object.
let bar = new foo();
1
  1. Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.
let bar = foo.call(obj2);
1
  1. Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object.
let bar = obj1.foo();
1
  1. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object.
let bar = foo();
1

That's it. That's all it takes to understand the rules of this binding for normal function calls. Well... almost.


There are a few exceptions. Arrow functions are one of those exceptions.

Arrow functions use lexical scoping for determining the value of this. Instead of using the four standard this rules, arrow-functions adopt the this binding from the enclosing (function or global) scope. Simply put, an Arrow function will use whatever value you would get for this if you wrote it on the line above where the function is declared.

# Call, Apply, Bind

Calling a function can be accomplished by writing the name of the function followed by one of these three methods.

The difference between them is that bind creates a copy of the function with the new context for you tto use later. call and apply will both call the function immediately with the new context.

function f1(a, b) {
  console.log(this);
  return a + b;
}

let answer1 = f1.call(window, 10, 5);
// returns 15 (parameters passed separately)
// will console.log the Window Object
let answer2 = f1.apply(document, [10, 5]);
// returns 15 (parameters passed in an array)
// will console.log the document Object
let answer3 = f1.bind(document.body);
//answer3 is now a copy of the function f1
// when we run answer3, document.body will be the value for `this`
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Prototypes

Every type of Object has a prototype. A prototype is a special kind of an object that contains all the methods that will be shared by all Objects of that type.

You will hear a lot about prototype and class over the next few semesters. They are two different approaches to designing and architect software. The problem is that in your early days of programming they can seem like almost the same thing.

We will try to help you understand the differences here in simple practical terms that will let you write better JavaScript with fewer unexpected errors.

A Class is a blueprint for building objects. It is not an object itself, just the plans for building a certain kind of object. Classes inherit properties and methods from parent classes. When you create (instantiate) an object from a class, the object will be given copies all the properties and methods from it's class blueprint as well as copies of all the properties and methods from all the ancestor parent classes. So, when you call an Object's method, the method actually exists inside the Object.

A prototype is an example Object. It is an Object. Think of it as the first one built. In JavaScript, when we create an Object a constructor function is used to build the object. That function has a prototype object. We can put any methods that we want to share with all the objects built with that constructor into that prototype object. We can still link our objects to parent ones but we don't copy the methods, instead, we just link to the parent's prototype. There is a chain of prototype type objects. When we create (instantiate) our Object, it doesn't need copies of all the methods and parent methods. If we call an Object's method and the method does not exist inside our Object, then JavaScript will look up the prototype chain for the method and delegate (borrow) the method to run.

JavaScript has something called the prototype chain, which is how inheritance works in JavaScript. Each one of the Object prototypes will have a connection to the prototype object belonging to it's parent object. At the top of the chain is the prototype of the Object object.

As an example, look at the toString() method. When you create an Array (opens new window), there is no method in Array called valueof. However, you can write the following and no error occurs.

let letters = new Array('a', 'e', 'i', 'o', 'u');
letters.valueof();
1
2

valueof reference (opens new window)

This works because of the prototype chain.

We are calling the method Array(). The Array function has a prototype object. All the methods that you would call on your array, like map or sort or filter are inside the Array.prototype object. The prototype object of Array.prototype is the Object.prototype object. (this is the prototype chain)

When the line letters.valueof() is run, the JavaScript engine looks inside of letters for a method called valueof. If it is not found then JS looks inside Array.prototype for a method called valueof. If the method is not found there, then JS looks inside Object.prototype for the method. Since Object.prototype.valueof does exist it can be run.

The prototype of Object.prototype is null. Once null is reached in the search through the prototype chain, then JS knows that it can safely say that an error has occured.

As this last video explains, a practical use of the prototype chain is to add new functionality to existing objects. Let's make a completely useless example of a method that we are going to add to all Array objects.

We will create a new method called bob and the purpose of this method is to change all the values of every element in an Array to bob. Got an Array filled with important numbers? Not any more. Now it is filled with bobs.

//adding the method bob to all Arrays
Array.prototype.bob = function () {
  //bob is now a function inside of Array.prototype object
  //You can think of Array.prototype as a name space where we are saving our function
  //The keyword `this` refers to the Array that is calling this method
  this.forEach(function (item, index) {
    //can't use arrow functions because they have a different `this` value
    //looping through all the values in the array and replacing them one at a time
    this[index] = 'bob';
  });
};

let robert = ['Robert', 'Robert', 'Robert', 'Robert'];
robert.bob();
//now the contents of robert are ['bob', 'bob', 'bob', 'bob'];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

So, that was just a fun example of how to add something into a prototype object.

The most important reason why we use the prototype is to be able to reuse functions and save memory.

Imagine if each time you created an Array, JavaScript had to copy all the methods into that Array.

let a1 = ['a', 'b'];
let a2 = ['c', 'd'];
let a3 = ['e', 'f'];
let a4 = ['g', 'h'];
let a5 = ['i', 'j'];
let a6 = ['k', 'l'];
//we have created 6 arrays
//imagine if we had to keep a copy of every array method inside each of those variables,
//along with the length property value and the actual values from the Array.
1
2
3
4
5
6
7
8
9

Array's have access to about 30 methods. With six arrays that would be ~180 functions that need to be held in memory. Now, add on all the methods from Object.prototype, for each of those arrays.

It would be a huge waste of memory.

Instead we get this prototype object where we can store and share all those methods. Plus we get the prototype chain and we can jump up through the list of prototype objects looking through all the shared methods.

# What to do this week

TODO Things to do before next week.

  • Read all the content from Modules 7.1, 7.2, and 9.1.
  • Continue working on the Hybrid Exercises
  • The first four Hybrid Exercises must be submitted before the Reading Week.
Last Updated: 5/31/2023, 8:15:38 PM