Creating Objects in JavaScript (4 Ways)
# 1. Object literal # 2. The new keyword # 3. Object.create() # 4. Class Notation1. Object literal
The first and most easily understood way to create an object is to literally just write one (hence, object literal). Hereās an example:
const person = { firstName: 'Luke', lastName: 'MacKenzie' };
This is much easier and cleaner than using the Object()
constructor function, which would be done like this:
const person = new Object();
person.firstName = 'Luke';
person.lastName = 'MacKenzie';
This brings us to the next way of creating objects:
2. The new
keyword
In the above example, we used the new keyword with a built-in constructor function, Object()
. This is not recommended for a few reasons:
- If you only need to create a single, one-off object, itās more code with no added benefit.
- If you potentially need to create a large number of objects, youād have to manually add the properties to every object. Imagine if we had hundreds or thousands of objects.
- It requires an additional process called āscope resolutionā behind the scenes to figure out if the constructor is built-in or user-defined.
So if the built-in Object()
constructor is no good, what do you do? Well, you write your own. Take a look at the example below:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
// notice how we donāt return anything (more on this below)
}
Getting back to the example, we can now use our constructor function like so:
const myself = new Person('Luke', 'MacKenzie');
A few things happen when we use the new
keyword with a constructor function:
- A brand new empty object gets created.
- The
this
keyword gets set to that empty object. return this
gets added to the end of the function. This is why itās important that we do not write our own return statement within constructor functions.- Adds a property onto the empty object called
__proto__
, which links the prototype property on the constructor function to the empty object.
To see this more clearly, take a look at what we get from our previous example:
As you can see, our Person
constructor created a brand new object, added the firstName
and lastName
properties to it, and then returned it.
This covers points 1, 2, and 3 of the list above, so letās dig into number 4 a bit.
To reiterate the point, whenever we use the new keyword to create an object, that object automatically gets a __proto__
property. This __proto__
property simply links our new object to the prototype of the constructor function, giving us access to any properties and methods that live on that prototype.
Continuing with our example, hereās proof that Iām not lying to you:
myself.__proto__ === Person.prototype;
// true
// alternatively, since __proto__ is technically deprecated, you could do this:
Object.getPrototypeOf(myself) === Person.prototype;
// true
This becomes super powerful when we want to share methods across objects, like so:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.logHi = function () {
console.log(`Hi, ${this.firstName}`);
};
const stewie = new Person('Stewie', 'Griffin');
stewie.logHi(); // "Hi, Stewie"
// now, we can keep adding methods to the Person prototype
// and our 'stewie' object (along with any other objects created with the Person constructor)
// will have access to those methods
Person.prototype.logBye = function () {
console.log(`Bye, ${this.firstName}`);
};
stewie.logBye(); // "Bye, Stewie"
You might be wondering, canāt we just add the methods within the constructor, like this:
Technically, you can. That said, if we use this version of the Person constructor to create 100 people, the logHi
function will be created 100 times which certainly isnāt optimal from a memory perspective. Instead, when we add the method to the Person prototype, the function is only ever created once and would be shared across the 100 people objects.
As a general rule, methods should be added to the prototype while properties should be set up inside the constructor since they will often have different values.
3. Object.create()
To start, letās quickly convert our Person
constructor function to a regular object:
const person = {
firstName: 'Default',
lastName: 'Default',
logHi: function () {
console.log(`Hi, ${this.firstName}`);
}
};
// ASIDE
// the logHi method above could have been defined with this shorthand:
const person = {
logHi() {
console.log(`Hi, ${this.firstName}`);
}
};
Now, since this is no longer a constructor function, we canāt use the new keyword. Instead, the idea here is to use this person object as the prototype, or the ābase objectā, for other objects. By doing so, we can simply modify or add to this object at any point in our application and any objects with the person object as its prototype will be able to access its properties and methods. That may sound a bit confusing so letās look at some code:
const quagmire = Object.create(person);
With this single line of code, weāve created a new object called quagmire
, and set its prototype to be the person
object. As a result, even though the quagmire object is technically empty, we can do this:
quagmire.logHi(); // "Hi, Default"
Okay, great - but what if I want it to log āHi, Quagmireā instead? Simple. We just add the same property to our newly created quagmire object, like this:
quagmire.firstName = 'Quagmire';
// Now, quagmire.logHi() will log "Hi, Quagmire"
You can think of this like weāre overriding or hiding the ādefaultā value. Whatās really happening, however, is that by adding a property with an identical name to the object itself, the JavaScript engine will find it there and will no longer look down the prototype chain. Maybe this picture will help make that a little clearer:
In summary, the Object.create()
method creates a new object, using an existing object as the prototype of the newly created object - all without having to define a constructor function.
4. Class Notation
With ES6 came a more straightforward approach of creating objects and setting the prototype. Keep in mind, however, that classes technically donāt add any new functionality to the language and as such are best described as āsyntactical sugarā.
So, how does it work?
The class
keyword starts a class declaration, which allows us to define a constructor as well as any other methods all in a single place. The constructor method is treated specially in that it provides the actual constructor function which will be bound to the class name (i.e. āPersonā in the example below). The rest of the methods are added to the constructors prototype.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
logHi() {
console.log(`Hi, ${this.firstName}`);
}
}
const stewie = new Person('Stewie', 'Griffin');
One last thing worth noting about this method is that constructors created with the āclassā notation will always complain if they are called without the new
keyword, even in non-strict mode.