JavaScript: Up and running with Object-Oriented Programming (OOP)
How to get started with object-oriented programming in Javascript, with some code examples (This is not a comprehensive course on object-oriented programming :p ) There will be absolutely no mention of polymorphism here.
Suppose you wanted to bundle data and functionality together, the best way to do this is with an object.
Objects are usually composed of data and methods, in key value pairs.
For example, I know a user object will have data like username and score, and some methods to update that data, such as updateScore(), login(), signout()
Note: Data is not updated directly, it is a good practice to update data in the object using the object’s functions/methods
Ok so let’s start to implement this..
To create an object in JavaScript we can use curly brackets.
const user = {};
Or we can create an object using the Object.create();
const user = Object.create();
To add properties to the object you can use dot notation or square bracket notation
user.username = "janedoe";
user["score"] = 0;
But this means we need to define all the properties every time we want to make a new user…. sounds repetitive right? We don’t want to define the same properties and functions again and again.
So… what’s a better way to do this?
We need a way to give our object built-in, ready to go methods every time it is created. We don’t want to re-define functions that are used across our objects.
The way to do this in JavaScript is with Prototypes. Prototypes are the mechanism by which JavaScript objects inherit features from one another.
So if an object refers to a prototype, it can have access to those functions defined in the prototype.
Every object and function (which are objects) have a prototype property… JavaScript will look there for additional functions that are available to this object. So if an object is of a type prototype we have defined elsewhere in our code, it can have those functions available to it without repeating them again and again.
A great example of this is how come every instance of an array has all of those built-in methods (
sort
,unshift
,pop, etc…
) It is because those methods live onArray.prototype
and when you create a new instance ofArray, you have access to those methods.
So let’s make a user “prototype” of reusable properties and methods.
- We define a function called User, this will act as the constructor for our User object. A constructor is the first method we call, usually to set up an object with the most basic, or default properties.
- We add methods to it using the .prototype property.
function User(username) {
this.username = username;
this.score = 0;}// Use .prototype to add methods to this user prototype
User.prototype.login = function () {
console.log('Login User');
}
User.prototype.signout = function () {
console.log('Signout User');
}
User.prototype.updateScore = function (score) {
console.log('Update Score');
this.score = score;
}
Then let’s create an object from this user!
const newUser = new User(“thecoolestuser”);
Hold on a second, where did this new keyword come from? Well, it turns out to instantiate an object and return an object of that prototype we just created, we need to use the new keyword. This is because we are not returning that object in the User constructor… Javascript needs to know to do this automatically for us (behind the scenes).
Without the return
statement, we wouldn’t ever get back the created object. We can do this manually, like so:
function User (username) {
const user = Object.create(User.prototype); // extra line
this.username = username;
this.score = 0;
return user; // extra line
}
Then we could do something like
const newUser = User(“thecoolestuser”);
But to make our lives easier, we usenew
keyword, those two lines are done for you implicitly (“under the hood”) and the object that is created is called this
. This way the binding is also done for you too. If we don’t, then the this
keyword won’t be created and it also won’t be implicitly returned.
Note: Arrow functions also don’t have a prototype
property, and cannot be used as constructors. Arrow functions don’t have their own this keyword.
So far, we implemented OOO without classes. If you did any courses in C++ or Java you might be screaming.
OK OK, so I know that in other languages we use a Class keyword. How come we didn’t use a class? What are classes?
A Class allows you to create a blueprint for an object. An instance of the class is created in the form of an object.
So remember that function User() that acted as our “constructor”. Instead of using classes, we just used a regular old JavaScript function to re-create the same functionality. This is because classes were not yet supported in JavaScript. In 2015, ES6, was released with support for Classes and the class keyword.
Ahuh so… now what? Well, we can rewrite the same thing using ES6 Classes!
- We use a class keyword instead of a function
- A constructor is used to set up and define the data properties
- All functions live inside the class object now, under the constructor. They will be automatically added to the prototype.
- User is still created using the new keyword, as before
class User {
constructor(username) {
this.username = username;
this.score = 0;
}
login() {
console.log('Login User');
}
signout() {
console.log('Signout User');
}
updateScore(score) {
console.log('Update score');
this.score = score;
}
}
const newUser = new User('mynewuser');
This is cleaner, and much nicer.
Suppose we wanted to branch off of the User class, and make another type of user with additional properties and methods, but we don’t want to modify the original user object. This is basically the concept of inheritance.
To do this we can use the extends property.
class Child extends Parent {};
To do this we can create an AdminUser extending User. All properties and methods of user are available to us.
class AdminUser extends User {
constructor(username, permissions, group) {
super(username); // super calls constructor of parent // properties specific to AdminUser
this.permissions = permissions;
this.group = group; } // methods specific to AdminUser getAllUsers() {
console.log('Get All Users');
}
}}
What the heck is super? You’ll see the super function inside the constructor of AdminUser. This function will call the parent class’s constructor. So for AdminUser, it’s parent is User. Well this is needed because first we must call the constructor of our parent, set all those properties in, then call our own constructor. Otherwise we would have empty properties.
The last thing you may come across is static methods. A static method is not officially part of the instance’s prototype, but is still related to the object. It might be a function that is related to users, but not a particular instance of the user. It belongs to the high level class. As such to invoke it, you use the Class not the instance. Confusing, I know.
To add a static method to a class, use the static keyword.
class User {
constructor(username) {
this.username = username;
this.score = 0;
}
login() {
console.log('Login User');
}
signout() {
console.log('Signout User');
}
updateScore(score) {
console.log('Update score');
this.score = score;
}
// static methods do not have access to the instance properties
// data must be passed to them static sortUsers(users) { }}
To invoke that method, we use the class not in the instance.
const newUser = new User('mynewuser');
newUser.updateScore(50);const newUser2 = new User('mynewuser2')const newUser3 = new User('mynewuser3');// Invoked using the class User, not newUser...const sortedUsers = User.sortUsers([newUser, newUser2, newUser3]);
Other useful things you can do with objects:
- Check if an object is an instance of a Class
object instanceof Class
Additional reading: