facebook youtube pinterest twitter reddit whatsapp instagram

Object Oriented Programming in JavaScript (The ES5 Way)

In this guide, you'll learn and understand how to create and use Objects in JavaScript (ES5).

While ES6 already supports creating classes (which is the way you create objects in Java, PHP, etc), they are not truly class-based like in the aforementioned language, but merely a convenient way to write OOP code in JavaScript, so under the hood, it's still using the older paradigm, so, let's learn how things work under the hood, and in a future guide, I'll be writing how you can write OOP code using the new ES6 syntax.

What is an Object?

To truly understand objects, we should maybe dive a bit into reality:

If you look around you, you’ll find different types of objects, with their own identity and properties, if you can realize the property, then you know the purpose of the object.

For example, houses, fans, tables, doors, etc are common objects around us. If you look at those objects, then you’ll see they differ from one another by some properties. Each and every object in its own state fulfill some purpose for us.

So for example, the fan object will have a component such as a fan motor, blade, electrical wiring, and the likes, all these components are what encompass the fan into becoming something that performs a purpose.

Now, keep the above information at your fingertips, while we dive into object-oriented programming in PHP.

Let's get back into programming:

The way objects work in JavaScript is not too different from how we experience objects in the real world.

In JavaScript, an object is a collection of properties, and if you remember above I said, if you can realize the property of an object, then you know its uses, it is no different in JavaScript, Objects can have properties such as variables, functions (also known as methods) that you declare within an object to perform certain tasks.

I know when I say object properties can be variable or functions, it might be confusing, but here is the thing, functions are nothing more than properties that hold function values, they are still properties of something.

The only difference between a standard function and a function declared within an object is that a method or a function within an object is designed to work with that object.  In addition to objects that are predefined (inbuilt ones) in the browser (DOM objects, Windows Objects. etc), you can define your own objects.

Again, with the real-life scenario, an object in JS is a whole entity, with properties and type, e.g in real life, a standing fan is an object with properties: it has a fan motor, blade, electrical wiring, weight, model, and the likes. This is no different in JS, JavaScript objects can have properties, which define their characteristics.

I hope you are getting me, if so, let's create our own objects, with properties and methods:

To create an Object car, you can simply do:

let Car = {
    color: 'Acura',
    engine: '3.0-liter single turbo',
    model: 'Type S',
    price:  30000 
};

To access the Car color, you simply do "Car.color", to access the engine, you do "Car.engine", etc, here is an example:

console.log(Car.color);    // => Acura
console.log(Car.engine);   // => 3.0-liter single turbo
console.log(Car.model);    // => Type S
console.log(Car.color);    // => 30000


Easy enough.

Properties of JavaScript objects can also be accessed or set using a bracket notation, in JavaScript, Objects are kinda close to an associative array, it kinda makes sense since each property is associated with a string value that can be used to access it. So, for example, you could access the properties of the Car object as follows:

Car['color'];   // => Acura
Car['engine']   // => 3.0-liter single turbo
Car['model'];   // => Type S
Car['color'];   // => 30000

Use whatever feels right to you, it's your preference anyway, however, there are cases where the square bracket notation is suitable, we would get to that in a later section.

So, a property can have a name (key) and a value. A property name (a.k.a key) may be any string, including the empty string, however, no single object may have two properties with the same name, this is like having two fan motors, stupid analogy, but you get the idea.

Querying and Setting Properties

We've already seen the way to obtain a value of a property by using either the dot(.) or square bracket ([]). Note that, the left-hand side should be an expression whose value is an object, eg Car.color, the left-hand side is the object - "Car", and the right-hand side is the property we are querying.

E.g, we can get a value of an object property and store in it another variable:

let color = Car.color    // => get the "color" property of the car, and store it in a variable color
let engine = car.engine  // => get the engine property of the car, and store it in a variable engine

You can dynamically set a new property and even change an existing one, e.g:

Car.year = 2020;   // create a year property of the car object
Car.price = 500    // Change the price property of the car object


JavaScript Property Descriptors

This is where thing gets a bit interesting, we've previously discussed property being a key and a value, now, here is the thing, every property has a property descriptor that we can use to see the attributes of that property, the attributes define a property behaviour, let's assume, I have the following object:

let Car = {
    color: 'Blue',
    price:  30000 
};

To check the property descriptor for the color property of the Car object, I can do the following:

console.log(Object.getOwnPropertyDescriptor(Car, 'color'));
// Output =>
{value: "Blue", writable: true, enumerable: true, configurable: true}

So, we are simply checking the property descriptor for the color property, and as you can see, in addition to the color property having a value, it also has writable, enumerable, and configurable attributes all set to true by default.

Here is what you can do with the property attributes:

  • value: the value of the property
  • writable: if true, the property can be changed
  • configurable: if false, the property cannot be removed nor any attribute can be changed, except its value
  • enumerable: if true, you can loop through the property.

Let's understand the attributes one after the order, we would skip the value as you probably know that is the value of the property (in our case, color property)

The Writable Attribute

The writeable attribute as you would expect defines if the property value can be changed from its initial value.

For example, to change the color property to non-writable, you can do the following:

Object.defineProperty(Car, 'color', {writable: false})

Now, if you check the attributes of the property, you would get false in the writable value:

console.log(Object.getOwnPropertyDescriptor(Car, 'color'));
// Output =>
{value: "Blue", writable: false, enumerable: true, configurable: true}

So, we can say the property color is non-writable, meaning, you can't change the initial value, if you try changing the value, it won't throw an error because we are not in strict mode, but it would for sure fail silently without changing the value, and you might not have an idea why things went wrong, so, you can write in strict mode like so, if you want an error thrown:

'use strict';
let Car = {
    color: 'Blue',
    price:  30000
};

Object.defineProperty(Car, 'color', {writable: false})  // => Set color property to false/non-writable
Car.color = 'Red'; // => Change the value of the color property

console.log(Car.color)

// Output =>
Uncaught TypeError: Cannot assign to read only property 'color' of object '

By using strict mode, you can eliminate some JavaScript silent errors by changing them to throw errors, and thus, avoid sloppy codes.

What if we change the color value to an object like so:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
    price:  30000
};

Object.defineProperty(Car, 'color', {writable: false})
Car.color = 'Red'; // => Change the value of here

console.log(Car.color)

Now, if you look at the console, you'll get an error, here is where things get interesting. If I change the value like so, it would work:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

Object.defineProperty(Car, 'color', {writable: false})
Car.color.one = 'Red'; // => Change the value here

console.log(Car.color)

// Output =>
// Object {one: "Red", two: "Orange"}

Why that though? Here is the thing, the color property is simply a pointer, and when you make it non-writable, you are only preventing that pointer from being changed. To fix this, just freeze the object from changing:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

Object.defineProperty(Car, 'color', {writable: false})
Object.freeze(Car.color) // => Freeze the damn object here and
Car.color.one = 'Red';   // => you won't be able to change this ;)
console.log(Car.color)
// Output =>
// Uncaught TypeError: Cannot assign to read only property 'one' of object '#

So, now, the entire color object would be read-only

The Enumerable Attribute

By default, properties on an object are enumerable, meaning we can loop over them using a for…in loop.

Here is an example:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

for (let item in Car) {
    console.log(item)
}

// Output =>
color
price

This is only giving us the property, to get the value, you can do:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

for (let item in Car) {
    console.log(item + ": " + Car[item])
}

// Output =>
color: [object Object]
price: 30000


As you can see I am using the square bracket notation to access the data, you can't use the dot operator because the name of the property is expressed as an identifier, you can't dynamically change it.

Like, you can't have Car.itembecause Identifiers must be typed literally into the program; they are not a data type, so they cannot be manipulated by the program, when you access a property of an object with the square ([]) array notation, the name of the property is expressed as a string. Strings are JavaScript datatypes, so they can be manipulated and created while a program is running, So, in the case, of Car[item[, the item can be changed while the program is running.

As you can see, we are looping over each property of the Car object, and each time through the loop, it returns the name of the property and assigns that to the item variable, so, we then use the property names using the square bracket notation to access it.

As I said previously, by default, properties on an object are enumerable, meaning we can loop over them using a for…in loop, we can actually change that behavior the following way:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

Object.defineProperty(Car, 'color', {enumerable: false});  // Set color enumerable to false

for (let item in Car) {
    console.log(item + ": " + Car[item])
}

// Output =>
price 30000

Interestingly, even though, we are still looping over all the properties in the Car object, the color property was not returned. That is one of the uses of the enumerable property.

Setting enumerable to false also makes it so the property does not show up in the object keys (the key is the name of the property, in my case, they are color, and price), here is how to look at that:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

Object.defineProperty(Car, 'color', {enumerable: false});  // Set color enumerable to false

console.log(Object.keys(Car)); // get all object keys of car

// Output =>
// Array(1) ["price"]

As you can see, the key returned only the price key without the color key, and that is because the color is not enumerable. If you want to make the color enumerable, you can simply set it to true, and the key would show up. When doing JSON serialization of an object, setting a certain property to non-enumerable won't return the property when serializing, but you'll be able to get the other properties.

Lastly, enumerable doesn't affect the ability to look at the property value, you can still do that just fine.

The Configurable Attribute

This is straightforward, while other attributes we've seen so far define the behavior of what you can do with a property, the configurable attribute property when set to false locks down a property to prevent certain attributes from being changed.

So, if you have:

Object.defineProperty(Car, 'color', {configurable: false});

You won't be able to

  • Change the enumerable attribute,
  • you cannot change the configurable attribute again,
  • and you cannot delete the property(e.g delete Car.color). You can, however, change the property's writable attribute

If you want to make it configurable, just remove the configurable:false from your code.

Getters and Setters

I didn't mention this, but getters and setter are also an attribute, and they are noting more than a function for either allowing you to specify the return value of a property using a function and or set the value of a property using a function, this can really make your code neat, and awesome, let's see an example of a getter, as usual since this is also an attribute we use defineProperty like so:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
        price:  30000
    };

Object.defineProperty(Car, 'colorAndPrice', {
    get: function () {
        return "Color and Price: " + this.color.one + " " + this.price;
    }
})
console.log(Car.colorAndPrice);

// Output =>
Color and Price: Blue 30000

This is really easy, I dynamically added a new property "colorAndPrice", and I set a getter attribute of how I want the return of my new "colorAndPrice" be defined. To access the property, you simply do Car.colorAndPrice, this is called getter, getting something.

To set, you do:

'use strict';
let Car = {
    color: {one: 'Blue', two: 'Orange'},
    price:  30000
};

Object.defineProperty(Car, 'setColor', {
    get: function() {
        return this.color.one + ' ' + this.color.two
    },
    set: function(newColor) {
        let colors = newColor.split(',')  // Splitting by comma delimiter
        this.color.one = colors[0]
        this.color.two = colors[1]
    }
})

Car.setColor = 'Black, White';   // setting a new value

console.log(Car.setColor);   // Black White, 
console.log(Car.color.one)   // Black
console.log(Car.color.two)   // White

Remember, setter is used to change a value of a property,  So, I passed "Black, White' as a new value to the setColor, which is then passed to the parameter in the set function(newColor), whatever is passed in there would be split based on a comma delimiter: let colors = newColor.split(','), still inside the function, we changed the property of both color.one, and color.2 to the indexes of what is splitted by the split function.

So, the getter can then return:

console.log(Car.setColor);   // Black White, 
console.log(Car.color.one)   // Black
console.log(Car.color.two)   // White

The getter can take effect because, we've changed the value. You would learn another way you can get and set directly from the constructor below.

Constructor Method and the 'this' Keyword

Well, all we've done so far is creating an object literal, object literal is a comma-separated list of name-value pairs inside of curly braces. This is fine in a couple of cases, but as soon as you create a couple of more instances of the object, you would get an error.

Imagine if you want to use certain properties over and over again, you'll want to create a new object with a different name which is redundant, the solution for this is creating a constructor, this way, you instantiate an object over and over again, without having to create more from scratch.

A constructor method is similar to a class-based method for creating and initializing an object of that class. (if you are used to PHP OOP, Java, etc, then you usually create a class that would allow for instantiating, JavaScript constructor mimics that, weird, but, that's how it works in JS)

function Human() {
    this.name = 'devsrealm'; 
    this.age  = 18;
    this.language = 'English';
    this.country = 'Angola';
}

To distinguish constructors from function, you must start your constructor with a capital letter, and when defining the properties, you use the (this) keyword.

I created a constructor with 4 properties and assigned a default value to each of the properties. Here is where it gets interesting, any objects I instantiate from the Human constructor will be prepolutated with the default data.

To create a new instance of a object, you can simply do

let James = new Human();

If you log "James", you'll see "James" as access to the default data:

console.log(James);
// => Human {name: "devsrealm", age: 18, language: "English", country: "Angola"}

I am using the Human constructor method as a template for creating the actual object – James, and I am doing that using the new operator. As you can see we are using the new operator in conjunction with the name of the constructor.

I can create as many object I want from that constructor, but there is a problem, we are hardcoding the properties values, and as you would expect, we won't be able to override the properties in the constructor. To do that, we can pass an argument instead of hardcoding, something like so:

function Human(name, age, language, country) {
    this.name = name; 
    this.age  = age;
    this.language = language;
    this.country = country;
}

This way, when we create a new Human, we can construct it with a name, age, language, and country, here is an example:

let James = new Human('James', 18, 'English', 'Scotland');

and when you log the variable, you'll see the values we passed as a taken effect:

console.log(James);
// => Human {name: "James", age: 18, language: "English", country: "Scotland"}

Now, you can create as many users with different property values:

let James = new Human('James', 18, 'English', 'Scotland');
let Devsrealm = new Human('Devsrealm', 14, 'English', 'US');

Interesting.  As usual, to get a specific property, you can do:

console.log(James.age)        // => get the value of the age property of James
console.log(Devsrealm.name)  // => get the value of the name property of Devsrealm


Easy enough.

You can also have a function in a constructor:

function Human(name, age, language, country) {
  this.name = name;
  this.age  = age;
  this.language = language;
  this.country = country;

  this.formatuser = function () {
    return "Name: " +      this.name + "\n" +
            "Age: " +      this.age + "\n" +
            "Language: " + this.language + "\n" +
            "Age: " +      this.country + "\n";
  }
}

The only difference here is we are adding a function, which is no different from a standard function except that it works within the constructor scope. Also, this keyword is used to refer to properties inside the constructor. This is like calling a regular variable, but only within the constructor.

Here is how I would instantiate a new object from the constructor:

let James = new Human('James', 18, 'English', 'Scotland');
console.log(James.formatuser());

// Output =>
Name: James
Age: 18
Language: English
Age: Scotland

If you ever wondered why I am using "James" with the dot operator (.) to get a property of a function, this is because James has reference to the object. This would be the same if I use another name, hope, I am making sense.

You can do a nested object, think of using the in-built Date constructor:

function Human(name, age, language, country) {
  this.name = name;
  this.age  = age;
  this.language = language;
  this.currentdate = new Date();
  this.country = country;

  this.formatuser = function () {
    return "Name: " +      this.name + "\n" +
            "Age: " +      this.age + "\n" +
            "Language: " + this.language + "\n" +
            "Age: " +      this.country + "\n" +
            "Date: " + this.currentdate + "\n";
  }
}

let James = new Human('James', 18, 'English', 'Scotland'); // Create a new object

// Output =>
Name: James
Age: 18
Language: English
Age: Scotland
Date: Thu Dec 10 2020 23:18:08 GMT+0100 (West Africa Standard Time)

You can also pass a value into the date property, just make sure to add as an argument in the constructor method, something like:

function Human(name, age, language, country, dob) {
  this.name = name;
  this.age  = age;
  this.language = language;
  this.currentdate = new Date(dob);
  this.country = country;

  this.formatuser = function () {
    return "Name: " +      this.name + "\n" +
            "Age: " +      this.age + "\n" +
            "Language: " + this.language + "\n" +
            "Age: " +      this.country + "\n" +
            "Date: " + this.currentdate + "\n";
  }
}

and when instantiating, you'll do something like so:

let James = new Human('James', 18, 'English', 'Scotland', '7-5-1999'); // Create a new object
console.log(James.formatuser());

// Output =>
Name: James
Age: 18
Language: English
Age: Scotland
Date: Mon Jul 05 1999 00:00:00 GMT+0100 (West Africa Standard Time)

Inbuilt Constructors

There are also inbuilt constructors that you would likely use, but just want to mention just in case:

Here is one for String:

let name = new String('the_devsrealm_guy');
console.log(name2) 

// Output =>
String {"the_devsrealm_guy"}
0: "t"
1: "h"
2: "e"
3: "_"
4: "d"
5: "e"
6: "v"
7: "s"
8: "r"
9: "e"
10: "a"
11: "l"
12: "m"
13: "_"
14: "g"
15: "u"
16: "y"
length: 17
__proto__: String
[[PrimitiveValue]]: "the_devsrealm_guy"

This would by default create a key of number with each string letters, you can also dynamically add a property and a value:

name.name= 'James';
console.log(name2);

// Output:
String {"the_devsrealm_guy", name: "James"}
0: "t"
1: "h"
2: "e"
3: "_"
....
16: "y"
name: "James"
length: 17
__proto__: String
[[PrimitiveValue]]: "the_devsrealm_guy"


Note: When comparing a value, you cannot compare a string with the inbuilt constructors, they are objects, and not string, this would be true for the rest of the inbuilt constructor. If you are curious, you can check the type using: console.log(typeof name), and you'll see it returns object.

The following are inbuilt constructors for: Number, Boolean, Function, Object (yeah, you can create an object of object), Arrays and functions:

// Number
let num1 = 3;                                       // regular number
let num2 = new Number(3);                           // Object

// Boolean
let bool1 = true;                                   // regular Boolean
let bool2 = new Boolean(true);                      // Object

The one I really like is the Function constructor, you would write a regular function like so:

// Function
let getSum = function(a, b){                       // regular function
  return a + b;
}

console.log(getSum(4,4));   // => 8

and you can use the inbuilt function constructor to instantiate an object like so:

let getSum = new Function('a','b', 'return a + b'); // object
console.log(getSum(4, 5));  // => 9

Haha, confusing right? The a, b I passed is the function argument just like how you would passed it in a regular function, and the return a + b is the function statement, again, same as a regular function, the first difference here is that this is an object, and lastly, you can pass any number of arguments, you want, but make sure the function statement is passed as the last argument, here is another example:

let getSum = new Function('a','b', 'c', 'return a + b + c'); // object
console.log(getSum(4, 5, 6));  // => 15

I really like the function constructor.

You can also do the same with an object, and an array:

// Object
let paul = {name: "paul"};                 // Object
let james = new Object({name: "james"});   // Object - Same as above, no difference

// Arrays
let arr1 = [1,2,3,4,5];                    // regular array
let arr2 = new Array(1,2,3,4,5);           // onject

That's it for an inbuilt constructor, you'll typically want to avoid using the inbuilt constructor, as you might have a problem when comparing values from one another.

Prototype Objects

JavaScript is a prototype-based language, and hence you need to pay attention to this section.

Each object in JavaScript has a prototype, and more so, the prototype is an object itself. To better understand prototypes, you need to think of them as the mechanism by which JavaScript objects inherit features from one another, even, in the dictionary, a prototype is a thing of which or whom copies, imitations, improved forms, representations, etc. are made.

The only way to understand a prototype is to understand the uses in the first place:

Prototypes ease the definition of methods, instead of having to define new methods every time a new object is instantiated, you can easily define methods to all instance of objects without having to redefine it every time, and hence this uses less memory, and it uses less memory because the methods in the prototype of that object are only stored in the memory once, but every new instance of the object has access to it.

When building prototypes, another thing you should keep an eye on is the elements of the base constructor that don't fit together or need to be handled differently.

Let's take an example, imagine you are a building a constructor around musical records that would contain musical artists, their songs, the genre of music they make plus a method to get the artiste summary, you would typically do something like so:

'use strict';
function MusicalRecords(artisteName,
                        artisteDescription,
                        songTitle,
                        genreName,
                        genreDescription) {

    this.artisteName         = artisteName;
    this.songTitle           = songTitle;
    this.artisteDescription  = artisteDescription;
    this.genreName           = genreName;
    this.genreDescription    = genreDescription;

    // methods to get the artiste details
    this.artisteSummary = function() {
        return `Name: ${this.artisteName} \n Song: ${this.songTitle}`
    }
}

let artiste = new MusicalRecords(
    'Avicii',
    'nan',
    'Silhouette',
    'nan',
    'nan');

console.log(artiste.artisteSummary());

// Output =>
/*
Name: Avicii
Song: Silhouette
*/

Can you spot any issues?

One, I am instantiating using an unnecessary constructor argument for it to return our method, here is how it looks in my IDE:

1. Unnecessary Constructor arguments

Note: a constructor should start with a capital letter to distinguish it from an ordinary function, made a mistake in the image above, sorry ;)

The one I am pointing arrow to is what I needed, and the other arguments I passed are redundant since the method artisteSummary() only needed the artisteName, and songTitle, I can change the way the argument are passed in the constructor, and just leave out the rest, but you might want to make sure, you are passing it in the right order, or else, you would get a different result, I can correct it like so:

let artiste = new MusicalRecords(
    'Avicii',
    'nan',
    'Silhouette',);

I can't leave out the second argument, because there won't be a way to specify I want the songTitle argument, you can change the order that they are passed in the constructor, this way, you can safely omit the redundant data, for now, I'll leave it as is, as I would be illustrating something with it when using prototypal inheritance.

To instantiate a new object I'll do:

//artiste 2
let artiste2 = new MusicalRecords(
    'Fela',
    'nan',
    'Colonial Mentality');
console.log(artiste3.artisteSummary());
// artiste 3
let artiste3 = new MusicalRecords(
    'Asa',
    'nan',
    'Jailer');
console.log(artiste3.artisteSummary());


For every new object you instantiate, it would recreate all of the properties plus the method in a new memory slot, which isn't something you want when creating a new instance of an object, since the artisteSummary method is common to all the object, it should only be in a single memory slot where all other objects can simply refer to when instantiating, and that is the use of using prototype.

Without using prototype, behind the scene, you are creating new methods and properties for each and every object being instantiated, and this leads to bloated objects with redundant properties and methods.

If for example, you want to add a new method (maybe getgenreinfo() method) to the MusicalRecords constructor, one way of avoiding the redundant data is creating a different constructor for different methods, doing things that way might also cause duplication, if for example, you want a certain method to behave identically for each constructor, you would need to make changes in both constructors.

So, here is what you can do, when building this kind of stuff, keep an eye on the elements of the base constructor that don’t fit together or that need to be handled differently, we move the ones that we feel might be unique to each object out of the base constructor, and leave the elements that can be generalized in the base constructor/class, in our case, we can move out the genre properties into its own constructor:

'use strict';
function MusicalRecords(artisteName,
                        artisteDescription,
                        songTitle) {

    this.artisteName = artisteName;
    this.songTitle   = songTitle;

    // methods to get the artiste details
    this.artisteSummary = function() {
        return `Name: ${this.artisteName} \n Song: ${this.songTitle}`
    }
}

// new constructor to handle different genre of music
function MusicalGenres(genreName,
                       genreDescription) {

    this.genreName        = genreName;
    this.genreDescription = genreDescription;

    // methods to get the genre details
    this.genreSummary = function() {
        return `Genre: ${this.genreName} \n Genre Description: ${this.genreDescription}`
    }
}

Easy enough, if we want to create artiste records, we instantiate from the MusicalRecords, and if we need a genre, we instantiate from the MusicalGenres without passing redundant data. Our work is not done, to avoid creating new methods copy in memory every time we instantiate an object, we can use prototype like so:

'use strict';
function MusicalRecords(artisteName,
                        artisteDescription,
                        songTitle) {

    this.artisteName = artisteName;
    this.songTitle   = songTitle;

    // methods to get the artiste details
    MusicalRecords.prototype.artisteSummary = function() {
        return `Name: ${this.artisteName} \n Song: ${this.songTitle}`
    }
}

function MusicalGenres(genreName,
                       genreDescription) {

    this.genreName        = genreName;
    this.genreDescription = genreDescription;

    // methods to get the genre details
    MusicalGenres.prototype.genreSummary = function() {
        return `Genre: ${this.genreName} \n Genre Description: ${this.genreDescription}`
    }
}

Instead of using this.genreSummary = function(){...}for our MusicalGenre constructor or this.artisteSummary = function(){...}we use a prototype instead, this would ensure that all the MusicalGenre objects have access to the genreSummary method, this is also the same as the MusicalRecords objects, any object you instantiate from the constructor would only reference the methods instead of being created in memory from scratch.

Prototypal Inheritance

Inheritance is the process by which one object can inherit properties or methods of another object. This lets the objects to share each other’s properties. This is another interesting thing you can do with a prototype.

For example, if you want to create an object that returns the number of streams of an artiste song, but you also want to inherit the method artisteSummarywith the rest of the properties, you can do it like so:

// get artiste no of plays constructor
function ArtistePlays(artisteName,
                      artisteDescription,
                      songTitle,
                      noOfPlays) {

    this.noOfplay = noOfPlay; // The new propertoes.
    MusicalRecords.call(this, artisteName, artisteDescription, songTitle)
}

// Inherting the MusicalRecords.prototype methods
ArtistePlays.prototype = Object.create(MusicalRecords.prototype)
let play = new ArtistePlays('Boy', 'nan', 'newsong');
console.log(play.artisteSummary());

// Output =>
/*
Name: Boy
Song: newsong
*/

When inheriting, you must pass the argument from the base constructor in the order that they are specified in the base constructor or class.

So, first thing first we passed in the properties arguments and added an extra one (noOfPlays, this is unique to the ArtistePlays constructor), we then use the call() method to copy over properties from one constructor into another constructor, this would avoid us from redefining the properties all over again, plus it is also inherited, so, whatever behavior you've done in the base class would always be copied above.

So, if for example, a property in the base constructor has to do with a calculation, we can always change the calculation in the base class, without having to change it in other places.

ArtistePlays.prototype = Object.create(MusicalRecords.prototype)

Since we need to also copy the method from the base class over, there is no way to do that with the call method, in this case, we use the object.create to create a new object and make it the value of the ArtistePlays.prototype, which would therefor inherit the MusicalRecords.prototype, this would copy all the methods in the MusicalRecords.prototype over, the good thing about doing things this way is that you are not creating a new method from scratch, you are simply referencing the one in the base constructor.

let play = new ArtistePlays('Boy', 'nan', 'newsong');

Since we are inheriting from the base constructor, we have to respect the order in which the argument are passed in, otherwise you won't get the value you expected, so, now, let's modify the getSummary method in the ArtistePlays controller to soothe what we want:

// get artiste no of plays constructor
function ArtistePlays(artisteName,
                      artisteDescription,
                      songTitle,
                      noOfPlay) {

    this.noOfplay = noOfPlay; // The new propertoes.
    MusicalRecords.call(this, artisteName, artisteDescription, songTitle)
}

ArtistePlays.prototype = Object.create(MusicalRecords.prototype)
ArtistePlays.prototype.artisteSummary = function() {
    return `Name: ${this.artisteName} \nSong: ${this.songTitle} \nNo of Plays: ${this.noOfplay}`
}

let play = new ArtistePlays('Boy', 'nan', 'newsong', 9999);
console.log(play.artisteSummary());

// Output =>
/*
Name: Boy
Song: newsong
No of Plays: 9999
*/

As you can see, to modify the method, you simply define it on ArtistePlays()'s prototype, it kinda make sense since it has a copy to the MusicalRecords.prototype.

I hope you are able to wrap your head around this, the way JavaScript does things is a bit crazy and weird to say the least, but it's not that hard to understand if you can realize how things work behind the scene.

Using Object.create

Another easier way you can create objects and prototype real quick is using object.create.

Let's see an example, without using object.create:

'use strict';
function MusicalRecords(artisteName,
                        songTitle) {

    this.artisteName = artisteName;
    this.songTitle   = songTitle;

    // methods to get the artiste details
    MusicalRecords.prototype.artisteSummary = function() {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`
    }
}

let Asa = new MusicalRecords('Asa', 'Jailer')
console.log(Asa.artisteSummary())

// Output =>
Name: Asa
Song: Jailer

With object.create:

const MusicalRecordsprototype = {
    artisteSummary: function() {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`;
    }
}

const artiste1 = Object.create(MusicalRecordsprototype, {
    artisteName: {value: 'Asa'},
    songTitle: {value: 'Jailer'}
});

console.log(artiste1.artisteSummary())

// Output =>
/*
Name: Asa
Song: Jailer
*/

When using object.create, you create the protype first with the functions or methods you want, you then instantiate the object with the properties you want. If you log the variable artiste1, you'll get:

{artisteName: "Asa", songTitle: "Jailer"}
artisteName: "Asa"
songTitle: "Jailer"
__proto__:
artisteSummary: ƒ ()
__proto__: Object

As you can see the method is in the __proto__, using the object.create is much faster than the one we used in the last section. To add more methods to the prototype, you can simply do:

const MusicalRecordsprototype = {
    artisteSummary: function() {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`;
    },
    genreSummary: function() {
        return `Genre: ${this.genreName} \n Genre Description: ${this.genreDescription}`
    }
}

Then you can instantiate a new object with the any of the prototype method you want:

const MusicalRecordsprototype = {
    artisteSummary: function() {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`;
    },
    genreSummary: function() {
        return `Genre: ${this.genreName} \nGenre Description: ${this.genreDescription}`
    }
}

const Dance = Object.create(MusicalRecordsprototype, {
    genreName: {value: 'Dance'},
    genreDescription: {value: 'Dance kinda of genre'}
})

console.log(Dance.genreSummary());

// Output =>
Genre: Dane
Genre Description: Dance kinda of genre

Easy enough.

You can also create a set method in your prototype, if maybe you want to change the Asa name to refer to another person, I can do:

const MusicalRecordsprototype = {
    artisteSummary: function () {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`;
    },
    setNewName: function (newname) {
        this.artisteName = newname;
    }
}

const artiste1 = Object.create(MusicalRecordsprototype, {
    artisteName: {value: 'Asa'},
    songTitle: {value: 'Jailer'}
});

artiste1.setNewName('devsrealm');

If you do this, you would receive:

Uncaught TypeError: Cannot assign to read only property 'artisteName' of object '#

You can fix this in two ways, you can either change the writable attribute of the property to True as we've done in the JavaScript_Property_Descriptors above or you use the following syntax:

const MusicalRecordsprototype = {
    artisteSummary: function () {
        return `Name: ${this.artisteName} \nSong: ${this.songTitle}`;
    },
    setNewName: function (newname) {
        this.artisteName = newname;
    }
}

const artiste1 = Object.create(MusicalRecordsprototype);
artiste1.artisteName = 'Asa';
artiste1.songTitle = 'Jailer';

artiste1.setNewName('devsrealm');
console.log(artiste1.artisteSummary());

// Output =>
/*
Name: devsrealm
Song: Jailer
*/

It's your choice, as you can see there are lots of ways you can create object in JavaScript, use whatever style you prefer, in the next guide, we would learn the ES6 ways of doing things, which is even dead simpler than this, until then, good luck.

Related Post(s)

  • Getting Started With JavaScript (Introduction)

    This is my first JavaScript guide on this blog, and in this guide, we would go over the introduction of JavaScript, but first… What is JavaScript? JavaScript is a client-side scripting language su

  • Operator in Javascript

    Operators in javascript can either be used for logical expression where you connect two or more expressions and can be a comparison expression where you compare two values. You can also use an operat

  • Exploring Data Types, and Variables in JavaScript

    In JavaScript or any programming language, a data type is an attribute of data that tells the interpreter how the programs intend to use the given data. JavaScript support a couple of data type whic

  • Creating a Loader and Remove it After a Specific Time Using JavaScript/CSS/SVG

    In this guide, we would create a loader, and remove it after a specific time using CSS and JavaScript. The icon would be in SVG format, and the rotation would be done in CSS, while the removal after

  • Working With The Local & Session Storage In JavaScript

    We previously take a deep dive into the Windows Object, Properties, Methods, and even the Document Object itself, in this guide, we would explore working with the local and session storage in JavaScr

  • Guide To Functions In JavaScript

    Functions in JavaScript or any programming languages are modular building blocks for creating powerful and modular scripts, using function makes it easy to isolate a code since it would be packaged u