Hi! Programming can be overwhelming π« but once you are comfortable with some basic concepts, it starts to feel like a superpower π¦ΈββοΈ and Javascript is one of the coolest languages to learn! π―
In this document you can find a summary of Object Oriented Programming in Javascript. We will cover:
- What is Object Oriented Programming?
- OOP in Javascript
- Classes
- Prototype Model
- Guiding Principles
What is Object Oriented Programming?
When we talk about Object Oriented Programming or OOP, we are really talking about how we organize our code. Many times, deciding where we put our code is not straight forward or easy. OOP gives us a paradigm to start working with.
The main point is to model things from real life using objects, which can contain data or properties, and can execute actions or methods. For example, a person can have many attributes such as name, age or nationality, and it can perform several actions such as running or eating.
If we want to create a lot of different people in our code, things can get out of hand very quickly.
OOP has several concepts that help us maintain our code manageable. We do no want Spaghetti Code π Some of them are:
- Abstraction βΒ We provide only essential information to the outside world while hiding their background details. As you build more and more complicated objects, abstractions helps us simplify things.
- Encapsulation βΒ Refers to storing all of our data and functionality into a single unit. It is also a way of restricting access to certain properties.
- Inheritance βΒ It is the ability to give data and functionality to an object based on another object. It helps us write a lot less code and promotes reusability.
OOP in Javascript
Javascript is a very flexible language, and you can use different paradigms to build your code. There are also a lot of strong opinions on the subject of using OOP with javascript, so take this article not as a definitive guide of how to do things, but rather a guide on what OOP is and how you can you use it in your advantage.
Different languages have different ways of doing things, and there is not necessarily a better way of doing things. In Javascript, you have several options to take advantage of the uses of OOP.
Objects is Javascript are like objects in real life, with things they have (properties) and things they do (methods).
You might have heard that "Everything is JS is an object". Although not entirely true, even things that are not objects, such as primitive types, can behave like objects sometimes because javascript wraps them into objects in the background.
let name = 'string' // primitive type
let name2 = new String('String object') // objects
The idea at the hearth of object oriented programming is that we are not limited to built-in objects. We can make our own.
Object literal notation
We can build our own objects using the object literal notation, with whatever properties and methods we want.
let userOne = {
email: 'some@email.com',
name: 'Pepe',
login() {
console.log(this.email, 'has logged in')
}
}
When the this
keyword is inside of an object, this refers to the object itself.
We can use dot nation to change data from our objects.
userOne.name = 'A name'
We can also user bracket notation.
userOne['name'] = 'Another name'
We can also create or define new properties or methods using dot or bracket notation, even if they don't already exist.
userOne.age = 28
However, we can start noticing some problems. It is probably better to put everything in the object literal notation, instead of adding new properties later in the code, so everything is encapsulated.
Also, if we want to create several object instances of user, we would need to repeat a lot of our code and work hard to change all users if we decide that the properties and methods of users need to change instead of them inheriting from one single source of truth. This is where we can tap into javascript functionality such as classes, constructors and prototypes to help us.
Prototype model vs Classes
There are not real classes built into javascript. Everything is based on the Prototype Model under the hood, but with ES6 we can emulate the idea of classes to make things a bit easier to understand. This is what we call syntactic sugar. You can use either, and we will explain how classes and the prototype model work.
Classes
Classes are blueprints of an object, but they are not specific. We identify and define that an object will have specific properties, but we don't specify their values.
The cool things in that when we create instances, they will already have all the basic functionality built-in, and we can pass values dynamically through parameters.
To use classes we define them using the keyword class
and the name of the class. By convention, we capitalize and CamelCase class names. Inside we put all data and functionality (properties and methods).
class User {
}
Constructor
Although you may see similarities between class and object syntax, there is one important method that sets them apart. Itβs called the constructor method. JavaScript calls the constructor() method every time it creates a new instance of a class.
An instance is an object that contains the property names and methods of a class, but with unique property values.
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Inside of the constructor() method, we use the this
keyword. In the context of a class, this
refers to an instance of that class.
We don't want to hard-code the data, we want to pass it as arguments.
To create a new instance we use the new
keyword.
class User {
constructor(name, email){
this.name = name;
this.email = email;
}
}
let userOne = new User('el@pepebenitez.dev', 'Pepe')
The new
keyword does 3 things:
- Creates an empty object { }
- Sets the value of
this
to the new empty object - Calls the constructor method, runs the code inside of it, and then returns the new instance
Class methods
To use methods, we don't define them inside of the constructor, we do it outside of it. You cannot use commas between the methods.
We have access to the this
keyword inside of methods as well.
class User {
constructor(name, email){
this.name = name;
this.email = email;
}
logIn(){
console.log(this.email, 'just logged in')
}
logOut(){
console.log(this.email, 'just logged out')
}
}
let userOne = new User('el@pepebenitez.dev', 'Pepe')
userOne.logIn()
// el@pepebenitez.dev just logged in
The syntax for calling methods and getters on an instance is the same as calling them on an object β append the instance with a period, then the property or method name. For methods, you must also include opening and closing parentheses.
Class inheritance
When multiple classes share properties or methods, they become candidates for inheritance β a tool we can use to decrease the amount of code we need to write.
With inheritance, you can create a parent class (also known as a superclass) with properties and methods that multiple child classes (also known as subclasses) share. The child classes inherit the properties and methods from their parent class, by extending them from the parent.
When we call extends in a class declaration, all of the parent methods are available to the child class. In addition to the inherited features, child classes can contain their own properties, getters, setters, and methods.
With inheritance we can pass al the basic functionality easily into other classes
class Cat extends Animal {
constructor(name, usesLitter) {
super(name);
this.usesLitter = usesLitter;
}
}
Letβs pay special attention to our new keywords: extends
and super
.
The extends
keyword makes the methods of the parent class available inside the child class.
The super
keyword calls the constructor of the parent class. In this case, super(name) passes the name argument of the Cat class to the constructor of the Animal class.
usesLitter is a new property that is unique to the Cat class, so we set it in the Cat constructor.
IMPORTANT
In a constructor(), you must always call the super method before you can use the this keyword β if you do not, JavaScript will throw a reference error. To avoid reference errors, it is best practice to call super on the first line of subclass constructors.
You can find more resources on Javascript classes here:
Prototype Model
Remember that we are just using pretend classes. The javascript prototype model was the original way to create or emulate using classes before the class keyword, and under the hood javascript is still using the prototype model.
Some people don't like the use of classes in Javascript.
Is "Class" In ES6 The New "Bad" Part?
So knowing how the prototype model works will help us become better developers, have more flexibility and be more efficient at debugging.
Constructor Functions
We still need to use a constructor function. A constructor function is what creates objects.
function User() {}
let userOne = new User()
We can still use the new
keyword. And it is still doing the same things (creating a new object, binding the context of the this
keyword equal to that object, and passes it to the constructor function). We also have access to the this
keyword.
function User() {
this.name = '';
this.email = '';
this.online = false;
this.logIn = function() {
console.log(this.email, 'has logged in')
};
}
let userOne = new User()
However, we don't usually put our methods inside of the constructor function. That is when we use the prototype property.
Prototype property
All objects have a prototype property. A prototype is like a map for the object type and its methods. We use the prototype property to store and define our methods.
function User() {
this.name = '';
this.email = '';
this.online = false;
}
User.prototype.logIn = function(){
this.online = true;
console.log(this.email, 'has logged in')
}
let userOne = new User()
This is all going on in the background when we use class.
Prototype inheritance
We can also use inheritance with prototype.
function Admin(...args){ // here we are using a rest parameter
console.log(args)
}
let admin = new Admin('admin name', 'admin@email.com')
We want to call the User constructor method from the Admin constructor method, so that it inherits the basic functionality.
function User() {
this.name = '';
this.email = '';
this.online = false;
}
function Admin(...args){ // here we are using a rest parameter
User.apply() // takes the user constructor function and runs it
}
let admin = new Admin('admin name', 'admin@email.com')
In the apply method we want to pass two parameters:
- What is the context of
this
. Sounds weird but we just need to passthis
because we are calling it inside of a constructor function, sothis
in that context refers to the instance to which we are applying the new inherited properties and methods. - The actual arguments we are receiving in an array format.
function User() {
...
}
function Admin(...args){ // here we are using a rest parameter
User.apply(this, args) // takes the user constructor function and runs it
this.role = 'Administrator'
}
let admin = new Admin('admin name', 'admin@email.com')
If we want to inherit methods, we use the prototype property of the child object.
Admin.prototype = Object.create(User.prototype)
You can see that using classes can make some thins a lot simpler and easier, but it is still very good to understand what is actually going on.
Guiding principles
When we are talking about OOP, it is difficult not to mention the SOLID principles. There principles are meant to guide us when we are designing our code structure. Two good goals to have in mind is for everything to have only one responsibility and that everything should work individually as we want them to, without relying on other objects:
Single Responsibility Principle
One of the most important things to remember is the Single Responsibility Principle which states that a class (or object) should only have one responsibility. This doesnβt mean that an object can only do one thing, but it does mean that everything an object does should be part of one responsibility.
Loosely Coupled Objects
Obviously, all of our objects are intended to work together to form our final application. You should take care, however, to make sure that your individual objects can stand alone as much as possible. Tightly coupled objects are objects that rely so heavily on each other that removing or changing one will mean that you have to completely change another one.
You can read more about the SOLID principles here:
5 Principles that will make you a SOLID JavaScript Developer
S.O.L.I.D The first 5 principles of Object Oriented Design with JavaScript
Bonus
Static Methods
Sometimes you will want a class to have methods that arenβt available in individual instances, but that you can call directly from the class You can use the static keyword to define static methods that can only be called from the class and not from the instance.
class Animal {
constructor(name) {
this._name = name;
this._behavior = 0;
}
static generateName() {
const names = ['Angel', 'Spike', 'Buffy', 'Willow', 'Tara'];
const randomNumber = Math.floor(Math.random()*5);
return names[randomNumber];
}
}
Because of the static keyword, we can only access .generateName() by appending it to the Animal class.
console.log(Animal.generateName()); // returns a name
const tyson = new Animal('Tyson');
tyson.generateName(); // TypeError
Method chaining
A little trick to call several methods with less code.
userOne.logIn().logOut()
In order to do this we need to return the instance of the object. The instance is stored in the keyword this
. We just need to return this
at the end of every method.
class User {
constructor(name, email){
this.name = name;
this.email = email;
}
logIn(){
console.log(this.email, 'just logged in')
return this
}
logOut(){
console.log(this.email, 'just logged out')
return this
}
}
Useful resources on Javascript
JavaScript Tutorial: Learn JavaScript For Free | Codecademy
Hi! My name is Pepe πΎ, and I am from Panama in Central America π΄ππ΄ You can find me in linkedin, twitter or github.
- If you found this useful feel free to share it!
- If you have any questions, recommendations or general comments feel free to drop me a message!
Top comments (0)