Advanced JS Basics

Chapter 2 14 mins

Learning outcomes:

  1. Recap of JavaScript's advanced aspects

Introduction

Before we begin learning the advanced level aspects of JavaScript, it's more than imperative for us to first have a firm grasp over a couple of basic ideas of the language.

These include constructor functions; prototypes and how they both are used together to emulate the inheritance model of class-based OOP languages; and closures and how they are used to emulate private property's on objects.

Let's have a quick and brief recap!

Constructors

Unlike other languages, JavaScript formalizes constructors as functions called using the new keyword. These functions serve to create objects whose prototype refers to the object stored in the function's prototype property.

JavaScript pre-defines constructors for almost all of its data types and other APIs, in addition to which developers can create their own constructor functions as well.

Creating a constructor doesn't require anything special to be done - it's just the invocation using new that distinguishes normal function calls vs. constructor function calls.

When a function is called with the new keyword, the internal engine creates a new object; makes the prototype of this object equal to the prototype property of the function; assigns this to the new object; and finally executes the function.

Consider the code below:

function MarksList(list) {
    this.list = list;

    this.average = function() {
        var sum = 0;
        this.list.forEach(function(ele) {
            sum += ele;
        });
        return Number((sum / this.list.length).toFixed(1));
    }

    this.lowest = function() {
        return Math.min.apply(null, list);
    }

    this.highest = function() {
        return Math.max.apply(null, list);
    }
}
For a detailed guide on JavaScript numbers, their methods and the Math object, please refer to the respective chapters: JavaScript Numbers - Basics, JavaScript Number Methods and JavaScript Maths.

It defines a function MarksList() and within it some statements to initialise a couple of properties on each object created using new MarksList(). Let's now utilise this constructor and create a list of all the marks of a student in his 6 tests:

var m = new MarksList([67, 80, 95, 65, 79, 99]);

console.log(m.average()); // 80.8
console.log(m.lowest()); // 65
console.log(m.highest()); // 99

Although the class MarksList() is defined with very basic features, this code is sufficient to illustrate the idea of a constructor. First we create a MarksList object in line 1 and then call some methods defined on it, such as the method lowest() to get the lowest marks in the list.

In a similar way, we can keep on instantiating multiple objects out of MarksList(), each with a different list of marks, and likewise be able to maintain a test record of a class of students.

For a detailed guide on constructors please refer to JavaScript Object Constructors.

In short, constructors are powerful factories for producing objects, but inefficient without the usage of something known as prototypes.

Prototypes

The architecture of JavaScript isn't built around a true class-instance model, present in other OOP languages, such as Java. Instead it is based on the concept of prototypes.

In the language, by default, all objects have an internal reference to another object from which they inherit their properties. This is referred to as the prototype of the object.

Objects inherit properties from their prototype which may in turn inherit its properties from another prototype.

For example, if an object o refers to a property x, the property will be first searched for in the list of the object's own properties - and once no match is found, on its prototype's own properties. This goes on until a null prototype is reached, on the lowest-level Object.prototype object.

This traversal of searching a property from prototype to prototype gives rise to the feature of prototypal inheritance. In prototypal inheritance, properties travel along from a root object to branch objects via a prototype chain, with upper-level properties overriding lower-level ones.

Remember that properties don't actually travel down prototype chains - rather it all happens at runtime when referring to a property on an object.

In the code above for the constructor MarksList(), we defined three methods within the function's definition. This means that two separate instances of MarksList() will have two separate sets of methods, both doing exactly the same thing. Forget about this, imagine we had a 100 such instances - then what?

As you would surely agree, this function repetition is bad and definitely worth resolution. And the way we can solve it is by using a prototype!

Here's the plan:

Instead of defining the methods on each instance of MarksList, define them on the prototype of those instances i.e MarksList.prototype, and consequently get them to be inherited down the chain by the instances.

This is illustrated as follows:

MarksList.prototype.average = function() {
    var sum = 0;
    this.list.forEach(function(ele) {
        sum += ele;
    });
    return Number((sum / this.list.length).toFixed(1));
}

MarksList.prototype.lowest = function() {
    return Math.min.apply(null, list);
}

MarksList.prototype.highest = function() {
    return Math.max.apply(null, list);
}

As you can see here, the three methods are now directly defined on the object MarksList.prototype, which is the prototype of all instances of MarksList (m in this case); and not inside any constructor.

This means that the method would NOT be created again and again for each instantiation call to the constructor, but rather be created only once and used again and again by the instances of the constructor - thanks to prototypal inheritance.

For a detailed guide on prototypes please refer to JavaScript Object Prototypes.

Function closures

One of the most difficult and tiring-to-understand ideas of JavaScript, is the idea of closures. The name might be familiar, but not its inner working!

By nature, functions in JavaScript have a lexical environment.

What this simply means is that functions have access to all the variables that are in scope at the time and exact location of their creation.

Take note of the word 'exact location' here.

If you're curious to know how this is done internally, well then here's the deal:

At the time a function is created, the entire scope of variables that is available at that instant is put inside an internal slot of the function.

Now, whenever the function is called from anywhere, variable resolution is performed using the local scope of the function, and then using this internally saved lexical scope.

By definition, a closure is a just a combination of a function and its lexical environment which symbolises that all functions are closures.

Yup, this is true!s

But the term 'closure' used commonly in JavaScript indicates a special kind of a function - one that is returned from another function.

The beauty of this function it remembers all the variables of the outer function even after it has returned (by means of that same internal slot we were talking about above).

But even this type of a function is behaving just how all normal functions do i.e it saves its lexical scope. So there is actually nothing special about closure functions.

Remember that all functions in JavaScript are closures!

Following is a very basic usage of a closure:

function createLogger(msg) {
    return function() {
        console.log(msg);
    }
}

var sayHello = createLogger("Hello.");
var sayBye = createLogger("Take care! Bye.");

sayHello();
sayBye();

The function createLogger() simply spawns functions that will log a given msg. The way it works in line 7, and similarly in line 8 is detailed as follows:

  1. The function createLogger("Hello.") is called.
  2. A local variable msg is created in createLogger() with the value "Hello.".
  3. The inner function in line 2 is returned and assigned to the variable sayHello. It saves its lexical environment in an internal slot which includes the variable msg.
  4. sayHello() is called in line 10. It refers a variable msg, which is not available in the local scope.
  5. Searching is shifted to the lexical scope (in the internal slot) and the msg variable is retrieved from there. Consequently "Hello" is logged

And this is the simplicity and complexity of a closure - whatever you want to call it!

Moving on...

With the basics laid out briefly and understood completely, it's time that we start our care, and hit the pedal to enter into the world of advanced JavaScript.

Just make sure you understand the language's fundamentals solidly, since otherwise you'll be moving on to learn impossible JavaScript!

Things can become complicated later on, if you don't know the basics right.

In the next chapter we shall begin with an understanding of a new data type in JavaScript i.e symbols and that how they are used by the language to expose many of its internal operations.