This was originally posted on my site at https://martyhimmel.me on January 31, 2017. Like a number of others on dev.to, I've decided to move my technical blog posts to this site.
While discussing data types, we compared an object to a dictionary - a set of terms (properties or keys) and their definitions (values). Another way to think of an object is as a container for something. Here's an example:
var person = {
firstName: 'John',
lastName: 'Smith',
age: 24,
isMarried: true,
children: ['Jack', 'Judy'],
pet: {
type: 'dog',
name: 'Fido'
}
};
In this example, the person
object contains several different data types, including another object. Accessing each of those properties is done with dot notation.
console.log(person.firstName); // John
console.log(person.age); // 24
console.log(person.children[0]); // Jack
console.log(person.pet.name); // Fido
Objects can also contain functions. Here's an example, continuing with the person
object:
var person = {
firstName: 'John',
lastName: 'Smith',
... // The other properties
getFullName: function() {
return person.firstName + ' ' + person.lastName;
}
};
console.log(person.getFullName()); // John Smith
Just like the other properties of an object, you declare the property name and give it a value. In this case, the value is a function.
this
The getFullName
function could be written in a slightly different way, using the this
keyword.
var person = {
...
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
console.log(person.getFullName()); // John Smith
The result is the same. this
is a reference to the current object. I'll save the details of how this
works for a separate tutorial, as it can be a bit confusing until you understand all the rules for it. For this tutorial, we'll keep it simple though, and this
will always refer to the current/containing object.
Creating Objects with Constructor Functions
Up to this point, we've been manually creating every object. That's fine, but it makes for a lot of repetitious code. If we wanted 10 person
objects, we'd have to create 10 separate objects.
var person1 = {
firstName: 'John',
lastName: 'Smith'
};
var person2 = {
firstName: 'Jane',
lastName: 'Doe'
};
// and so on
There's a principle in programming called "DRY" - Don't Repeat Yourself. If you can avoid duplicating code (sometimes, you can't or don't necessarily want to), it makes the code easier to maintain. So, how do we use that principle here?
You'll notice each of our person
objects have the same properties - firstName
and lastName
. They could have all the properties from the first example, if we wanted, but we'll keep it simple here. In any case, the same code is repeated in creating every object.
This is where constructor functions come in handy. A constructor function is a function that produces objects. A common naming convention is to capitalize the first letter of a constructor function. This sets it apart from other functions. Otherwise, a constructor function is created in exactly the same way as any other function. Now, let's convert the above example.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
var person1 = new Person('John', 'Smith');
var person2 = new Person('Jane', 'Doe');
// and so on
console.log(person1.firstName + ' ' + person1.lastName); // John Smith
console.log(person2.firstName + ' ' + person2.lastName); // Jane Doe
Using the Person
constructor gets rid of the repititious code of assigning the first and last name to each object. Imagine if each person had 10 or 15 properties. That would be a lot of repeated code. Now imagine you had 500 people, then found a bug or needed to change some property or method of the object. You'd have to make 500 changes. Using a constructor function, you only have to change it in one place. This is why the DRY principle is important.
The Person
constructor takes two arguments - firstName
and lastName
. The use of this
inside the function is used to assign the values passed to the given property name. In other words, the firstName
argument passed to the constructor is assigned to the firstName
(this.firstName
) property of the created object.
Calling new
is what creates the actual object. If we look at the person1
object that was created, it looks like this:
console.log(person1); // {firstName: 'John', lastName: 'Doe'}
Object Prototypes
In this section, we'll continue using the above Person
constructor as our base. It would be convenient to have a getFullName
method - like in the first section. That's where prototypes come in.
JavaScript is a prototype based language. You can read about it in depth in Mozilla's developer docs.
Every object in JavaScript has a prototype. Logging an object to the console gives more info than just the object itself. So, a real view of console.log(person1);
would give us this (using Chrome):
Person
firstName: "John"
lastName: "Smith"
__proto__: Object
constructor: Person(firstName, lastName)
// a handful of other properties
prototype: Object
// more properties
__proto__: Object
// a bunch of properties inherited from the prototype chain
As you can see, there's a whole lot more going on than just the two properties we created in the constructor. That's all part of the prototype structure of JavaScript objects. The prototype allows objects to inherit properties from other objects. This also means we can retrofit objects with new methods by tapping into the prototype
property of the constructor.
The format for adding methods via the prototype chain is:
[constructor name].prototype.[method name] = function() {
// do something
};
Let's add the getFullName
method.
Person.prototype.getFullName = function() {
return this.firstName + ' ' + this.lastName;
};
console.log(person1.getFullName()); // John Smith
Now that the Person
constructor has a getFullName
method, that method is available to every instance - person1
, person2
, and any other objects that may have been created with the constructor. And because of the way prototypes work, even if a Person
object was created before the prototype method was added to the constructor (as is the case in this example - write the constructor, create two objects, add the prototype method), that method is still available to all objects created with the given constructor.
Top comments (0)