Javascript’s ‘new’ keyword goes hand-in-hand with the object oriented programming paradigm in which the code written aims to mimic things and actions in the real world. A dog can run and jump, a user can login and logout. In the world of Javascript, a single ‘dog’ would be an object, and the ‘run’ action would be a method found on that object.
Object oriented programming seeks to combine data and actions that we can invoke on that data while making it easy for us programmers to switch between the two.
Consider the following scenario: We have a user and that user wants to login to our eCommerce app, browse around, place an order and perhaps leave a review for a product they purchased several weeks ago.
First, let’s take a look at a naive approach that you will see very quickly will not scale well and will lead to a lot of memory usage:
const user1 = {
userId: "00001",
name: "Thomas",
displayName: "tom123",
email: "thomas.smith@example.com",
login: () => {
//login business logic
//maybe an API call, storing session info
console.log("User has successfully logged in!")
},
logout: () => {
//logout business logic
//Another API call, removing session info
console.log("User has successfully logged out!")
}
}
console.log(user1.displayName) //returns 'tom123'
user1.login() //logs the user in to the app
The above code is fairly straightforward. We create an object with several properties (userId, name, etc) and assigned it the label ‘user1’ in memory. This also has a few methods attached as well: login and logout.
The object oriented ‘litmus test’ is: Can we run user1.login() and actually access both the object and the functionality that we want?
While the answer is ‘yes’ for the above sample, it has several fairly obvious issues:
What if we have 100 users, or perhaps 100,000 users or even more? Every single user object will have to have copies of those two login and logout methods and waste a lot of memory in the process. Also, what if we have to change the business logic in either of those methods sometime in the future due to some enhancements the product team wants? You would be stuck trying to individually update thousands and thousands of methods and hope for no errors along the way.
This implementation will not scale well, however at its core this is exactly what we want in terms of features – it satisfied the object oriented litmus test of ‘Can we run user1.login() and actually access both the object and the functionality that we want?’
Let’s take a look at another implementation that uses Object.create() – Object.create(), if invoked without any arguments will return a blank object. If passed an argument that is itself an object with key/value pairs, it will do something pretty interesting that gets us pretty close to our ideal solution to implement an object oriented programming paradigm in Javascript.
const userFunctions = {
login: () => {
//login business logic
//maybe an API call, storing session info
console.log("User has successfully logged in!");
},
logout: () => {
//logout business logic
//Another API call, removing session info
console.log("User has successfully logged out!");
},
};
const user2 = Object.create(userFunctions);
console.log(user2.__proto__, "user2 proto");
user2.userId = "00002";
user2.name = "Sam";
user2.displayName = "Sam123";
user2.email = "sam@example.com";
user2.login();
When we console.log(user2.__proto__) we get to see the magic that is going on behind the scenes. Javascript will append an __proto__ (double underscore proto, sometimes called ‘dunder proto’) to each object and it will then in turn give access to the login and logout functions. The litmus test holds true: When we call user2.login() Javascript looks to the __proto__ property when it doesn’t find the login() method immediately on user2. Once it finds it, it then invokes login() and the body of the function then runs.
Finally, we can clean this up a little further and utilize some more modern ES2015 features: The ‘new’ keyword.
The ‘new’ keyword in Javascript automates 3 key tasks for us:
- It auto-creates an empty object and assigns it to the label ‘this’
- It sets up a hidden __proto__ property that points to the constructor function’s prototype object that contains all of the desired functionality
- It automatically returns the newly created object that was stored in the ‘this’ label in memory.
Let’s see it in action:
function CreateUser(userId, name, displayName, email) {
this.userId = userId;
this.name = name;
this.displayName = displayName;
this.email = email;
}
CreateUser.prototype.login = function () {
console.log("User has successfully logged in!");
};
CreateUser.prototype.logout = function () {
console.log("User3 has successfully logged out!");
};
const user3 = new CreateUser("00003", "Mike", "Mike123", "mike@example.com");
console.log(user3.__proto__, "user3 __proto__");
user3.login();
In this example, user3.__proto__ points to the prototype object on the CreateUser function – this happens thanks in part to the ‘new’ keyword. This functionality is made possible because when you create a function in memory, not only does Javascript save the function body, but it also creates a ‘hidden’ object that you can append key/value pairs to. This concept is sometimes referred to as a “function object combo”.
Javascript takes the above concepts even one step further by implementing classes, however that topic is better left for another article for another time. Thanks for reading and hopefully you’ve gained a better understanding of how the ‘new’ keyword is working under the hood!