Good JavaScript programming needs a process — a way to guide how you think about problems and how you write code to solve these problems.
Arbitrarily inventing and improvising your code’s structure is fine for a beginner, but any technical recruiter administering a JavaScript proficiency test will always consider this amateurish at best.
So stop improvising your code structure and writing spaghetti JavaScript.
How?
Use a programming paradigm.
In JavaScript, one of the best programming paradigms is the object-oriented paradigm.
It’s a set of principles that guide the logic of your code structure and how you should write your JavaScript.
And while it has technical aspects, it is not solely a technical component of JavaScript, as a function or loop is. It’s also a way of thinking about problems and a set of rules about how to write your code to solve these problems.
In its simplest interpretation, it’s nothing more than a way to structure your code.
In its most complex, it’s a set of methodologies to guide how you think about and solve problems with your JavaScript code.
This guide will help you understand and apply the object-oriented programming paradigm.
Object-oriented programming
Object-oriented programming relies heavily on objects and is based on four scary-sounding principles:
- encapsulation
- abstraction
- inheritance
- polymorphism
Each principle is applied through JavaScript objects, meaning you’ll be using objects to structure and organize your program.
You’ll still use functions and loops and all your favorite JavaScript methods, but they will all be organized within and around the capabilities of objects.
Let’s explore these principles to help you understand what they mean.
Encapsulation
Or, in simpler terms — keep related parts of your code grouped together.
Your JavaScript programs will always have portions of code that are directly related to each other.
The principle of encapsulation dictates that when you have code that can be grouped together, you group it together.
Take a look at the code below, do you see any groups that the statements can be organized into?
let dogBreed = "Golden Retriever" let dogColour = "Yellow"; let dogAge = 2; let dogSound = function() { alert("Woof Woof!"); }; let catBreed = "Tabby"; let catColour = "Grey"; let catAge = 3; let catSound = function() { alert("Meooooow!"); }
Right, the two categories are fairly obvious: dog and cat.
Following the principle of encapsulation, let’s reorganize our code with the use of JavaScript objects.
Here’s what that looks like:
let dog = { breed: "Golden Retriever", colour: "Yellow", age: 2, sound: function () { alert("Woof Woof!"); }, }; let cat = { breed: "Tabby", colour: "Grey", age: 3, sound: function () { alert("Meooooow!"); }, };
That’s really it, to apply this principle you just need to make sure that when you have code that can be grouped together, you group it together.
And with the use of the encapsulation principle, we can ensure that data and methods are all grouped together, thereby making your code easier to understand and maintain.
Abstraction
Or, in simpler terms — keep the JavaScript objects you create simple.
Objects have a tendency to become unnecessarily complex as they are versatile and have no limit to the number of methods or properties they can contain.
The principle of abstraction encourages you to keep your object simple by only using the methods and properties which are essential to the object to perform it’s desired output.
Let’s consider a TV through the lens of abstraction to arrive at a better understanding of the concept.
A TV does many things, it can turn on, turn off and switch to specific channels, and you can even cast content to it from your phone.
A TV also has many properties: weight, size, colour, age, and brand.
Through the lens of abstraction, if we were to create a JavaScript object to allow the TV to turn on, that object should only contain the properties and methods directly related to making the TV turn on, everything else is irrelevant.
In other words, the object is an abstraction of the TV as a whole. It is a simplified version of a TV as it does not contain all the properties and capabilities of a TV, but only what is necessary to allow the TV to turn on.
Let’s write some code to demonstrate how abstraction works for this TV object designed to turn the TV on.
Here’s what the object might look like without abstraction.
turnOnTV = { tvAge: 4, tvColour: "black", tvHeight: 45, tvBrand: "Samsung", turnOn: function() { console.log("TV is on"); } }
Here’s what the object looks like with abstraction.
turnOnTV = { turnOn: function() { console.log("TV is on"); } }
Notice how the second version only contains what is necessary to allow the TV to turn on?
That’s the essence of abstraction. Only put in your objects what is necessary.
Inheritance
Or, in simpler terms — reduce repetition.
When using objects in JavaScript we often end up with a lot of very similar objects. Many times you’ll need an object to have the same method or property as an existing object.
Object-oriented programming seeks to minimize the confusion that similar objects have through inheritance.
This is a bit of a tricky concept, so let’s use an analogy of a library.
Think of a library with thousands of books. While each of these books may be unique, they are all still books, and books can all be read.
Thinking of this through the principle of abstraction and objects, we’d want to have a single instance of the read method that is shared (inherited) by all books.
We can do this with the use of a JavaScript prototype.
A prototype allows you to create objects that are based on a single, master object. In JavaScript, this process is called instantiation.
For books, we’d want our prototype to contain a read function so that any time a new book object is instantiated from it, it contains the same read method, saving us from having to recreate the same read method for every book.
Let’s use some code to help illustrate the concept of inheritance and prototypes in action.
First, let’s create a prototype for the book.
function Book(title, author, year, category) { this.title = title; this.author = author; this.date = year; this.category = category; this.read = read() { console.log("You are reading this book!"); } }
Next, let’s instantiate a new object based on the prototype.
function Book(title, author, year, category) { this.title = title; this.author = author; this.date = year; this.category = category; this.read = function() { console.log("You are reading this book!"); } } let book1 = new Book("Dune", "Frank Herbert", 1965, "science fiction");
Now that you’ve instantiated a new object from the prototype, try calling the function from the Book
prototype with the book1
object in your JavaScript console.
Notice how the book1
object has the read
method even though it wasn’t specified when you were creating it?
The book1
object has the read method because it inherited it from the Book
prototype.
With the use of the inheritance principle, we can ensure that data and features used by multiple objects are contained in a single source, thereby reducing the need for repetition and making your code easier to maintain.
Polymorphism
Or, in simpler terms — your JavaScript statements can have varied outputs.
The concept of polymorphism is the trickiest of the four principles for me. It took me quite some time to wrap my head around the concept and how to apply it, but I think the explanation that follows will help you avoid some of the headaches I experienced.
The word polymorphism is ancient Greek and translates roughly to many kinds. In JavaScript, this means a statement can have many varied outputs.
Think of the prototype concept we just explored in the previous section which can pass on a method or property to an instantiation.
Polymorphism allows each instantiation to produce its own unique output when using a method or property inherited from a prototype.
Let’s look at an example using animal sounds to better understand how polymorphism works.
First, let’s create an Animal
prototype that receives a parameter for the sound an animal makes.
function Animal(sound){ this.sound = sound; this.speak = function(){ return this.sound; } }
Next, let’s instantiate three objects from the Animal
prototype and give them each a unique sound
.
function Animal(sound){ this.sound = sound; this.speak = function(){ return this.sound; } } var dog = new Animal("woof"); var cat = new Animal("meow"); var cow = new Animal("moo");
Next, let’s create a function to display each of the sounds in the console.
function Animal(sound){ this.sound = sound; this.speak = function(){ return this.sound; } } var dog = new Animal("woof"); var cat = new Animal("meow"); var cow = new Animal("moo"); function showInfo(obj){ console.log(obj.speak()); }
And finally, let’s log each of the sounds to the console.
function Animal(sound){ this.sound = sound; this.speak = function(){ return this.sound; } } var dog = new Animal("woof"); var cat = new Animal("meow"); var cow = new Animal("moo"); function showInfo(obj){ console.log(obj.speak()); } showInfo(dog); showInfo(cat); showInfo(cow);
Notice how each instantiation of the Animal
object passes its own unique argument to the same speak
method and a single speak
method has three different outputs? This is polymorphism.
Within the Animal
prototype there is a single method speak
. Within each instantiation of this prototype (dog
, cat
and cow
) the method expresses different outputs.
So, to reiterate, your JavaScript statements can have varied outputs. This is polymorphism.
Applying the principle of polymorphism allows you to keep your code organized and efficient by minimizing the need for repetition.
Applying Object-oriented programming methodology to your problem solving
To use the object-oriented programming methodology effectively you must teach yourself how to look at problems through an object-oriented lens.
Now that we’ve covered the four principles of object-oriented programming: encapsulation, abstraction, inheritance, and polymorphisms, I want to share my approach to looking at problems through an object-oriented lens.
But first, I want to acknowledge that there are many articles expressing countless views on how you can, should, and should not think about programming in JavaScript.
I suspect that over time JavaScript developers each develop a unique approach to writing JavaScript and that every one of them will profess to you that their approach is the best.
I’m not going to argue that my approach is the best, but simply that it works for me.
My approach is simple and relies on following the steps:
- Deconstructing each part of your problem.
- Encapsulating each part into an object.
- Ensuring each object is an abstraction and contains only what is necessary to arrive at the desired output.
- Applying Inheritance and polymorphism to reduce complexity.
Let’s take a try using the four steps above on this Netflix homepage.
First, let’s deconstruct the parts of the homepage.
I see three main parts: the navigation, the hero image, and the “Popular on Netflix” slider.
Now that we’ve deconstructed the page, let’s create an object for each component.
let navBar = {}; let heroImage = {}; let popSlider = {};
Now, let’s use abstraction to add properties and methods to the navBar
and heroImage
objects.
let navBar = { logo: "netflix-logo.jpg", homeBtn: "netflix.com", tvBtn: "netflix.com/tv", moviesBtn: "netflix.com/movies", latesBtn: "netflix.com/latest", myListBtn: "netflix.com/list" }; let heroImage = { mainImage: "theLastDance.jpg" playBtn: function() { console.log("starting your show"); }, moreInfoBtn: function() { console.log("here's more info"); } }; let popSlider = { };
Seeing as the popSlider
object has a number of movies, we’ll use the principle of inheritance and create a prototype to avoid repetition.
let navBar = { logo: "netflix-logo.jpg", homeBtn: "netflix.com", tvBtn: "netflix.com/tv", moviesBtn: "netflix.com/movies", latesBtn: "netflix.com/latest", myListBtn: "netflix.com/list" }; let heroImage = { mainImage: "theLastDance.jpg", playBtn: function() { console.log("starting your show"); }, moreInfoBtn: function() { console.log("here's more info"); } }; function popSlider(title, year, category) { this.title = title; this.date = year; this.category = category; this.movie = function() { console.log(this.title); } }
Now let’s instantiate some objects from the popSlider
prototype.
let navBar = { logo: "netflix-logo.jpg", homeBtn: "netflix.com", tvBtn: "netflix.com/tv", moviesBtn: "netflix.com/movies", latesBtn: "netflix.com/latest", myListBtn: "netflix.com/list" }; let heroImage = { mainImage: "theLastDance.jpg", playBtn: function() { console.log("starting your show"); }, moreInfoBtn: function() { console.log("here's more info"); } }; function popSlider(title, year, category) { this.title = title; this.date = year; this.category = category; this.movie = function() { console.log(this.title); } } let pop1 = new popSlider("Have a Good Trip", 2020, "documentary"); let pop2 = new popSlider("The Last Dance", 2020, "documentary"); let pop3 = new popSlider("Deadpool 2", 2019, "action"); let pop4 = new popSlider("Jerry Seinfeld", 2018, "comedy"); let pop5 = new popSlider("Into the Night", 2017, "suspense");
Finally, let’s use the principle of polymorphism to use our inherited movie
method to list the title of each of our popSlider
instantiations.
let navBar = { logo: "netflix-logo.jpg", homeBtn: "netflix.com", tvBtn: "netflix.com/tv", moviesBtn: "netflix.com/movies", latesBtn: "netflix.com/latest", myListBtn: "netflix.com/list" }; let heroImage = { mainImage: "theLastDance.jpg", playBtn: function() { console.log("starting your show"); }, moreInfoBtn: function() { console.log("here's more info"); } }; function popSlider(title, year, category) { this.title = title; this.date = year; this.category = category; this.movie = function() { console.log(this.title); } } let pop1 = new popSlider("Have a Good Trip", 2020, "documentary"); let pop2 = new popSlider("The Last Dance", 2020, "documentary"); let pop3 = new popSlider("Deadpool 2", 2019, "action"); let pop4 = new popSlider("Jerry Seinfeld", 2018, "comedy"); let pop5 = new popSlider("Into the Night", 2017, "suspense"); function showInfo(obj){ console.log(obj.movie()); } showInfo(pop1); showInfo(pop2); showInfo(pop3); showInfo(pop4); showInfo(pop5);
And there you have it, you’ve now got a working understanding of object-oriented programming and are well on your way to becoming an intermediate JavaScript developer.
This is one of the most challenging topics for beginners, so congratulations on making it this far.