In This Chapter
• Understand at a deeper level how Objects work
• Learn to create custom objects
• Demystify the prototype property
• Do some inheriting
In the Introduction to Objects schtuff in Chapter 11, “Of Pizzas, Types, Primitives, and Objects,” I provided a very high-level overview of what objects in JavaScript are and how to think about them. That was good enough to cover the basics and some of the built-in types, but we need to go a little deeper. In this chapter, we will make that earlier chapter seem like the tip of a ginormous iceberg:
What we are going to do here is re-look at objects in greater detail and touch on the more advanced topics such as using the Object
object, creating your own custom objects, inheritance, prototypes, and the this
keyword. If all that I’ve listed so far makes no sense, it will after you’ve reached the end of this chapter...I hope.
Onwards!
At the very bottom of the food chain, you have the Object
type that lays the groundwork for both custom objects as well as built-in types like Function, Array
, and RegExp
. Pretty much everything except null
and undefined
is directly related to an Object
or can become one as needed.
As you saw from the introduction to objects forever ago, the functionality that Object
brings to the table is pretty minimal. It allows you to specify a bunch of named key and value pairs that we lovingly call properties. This isn’t all that different from what you see in other languages with data structures like hashtables, associate arrays, and dictionaries.
Anyway, all of this is pretty boring. Let’s get to some of the more exciting stuff!
All the cool kids are creating objects these days by using the funny-looking (yet compact) object literal syntax:
var funnyGuy = {};
That’s right. Instead of typing in "new Object()"
like you may have seen in old-timey books, you can just initialize your object by saying "{}"
. At the end of this line getting executed, you will have created an object called funnyGuy
whose type is Object
. If all of this makes sense so far, great!
This funnyGuy
object isn’t as simple as it looks. Let’s dive a little bit deeper and visualize what exactly is going on. On the surface, you just have the funnyGuy
object:
If you back up and look more broadly at the funnyGuy
object, you’ll realize that it isn’t alone here. Because it is an object, is has a connection to the main Object
type that it derives from:
What this connection means is pretty significant. Let’s add some more detail to what we have provided so far:
The funnyGuy
object has no properties defined. That makes sense given what we specified in the code (and can see in the diagram above):
var funnyGuy = {};
Our funnyGuy
is simply an empty object. While there may not be any properties we defined on it, there is a special internal property that exists called __proto__
and often visualized as [[Prototype]] that points to the internally defined Object
. If your mind just melted for a second, let me slow down and explain what is going on here.
What the __proto__
property references is what is known as a prototype
object:
A prototype object is the source that another object is based on. In our case, the funnyGuy
object is created in the likeness of our Object
type. What this means is best highlighted by looking at an example. We know that funnyGuy
contains no properties of its own. Because it is “derived” from our Object
type, you can access any properties the Object
contains through funnyGuy
itself.
For example, I can do something like this:
var funnyGuy = {};
funnyGuy.toString(); // [object Object]
I am calling the toString()
method on our funnyGuy
object. Despite funnyGuy
not having any property called toString
on it, what gets returned isn’t an error or undefined
. You actually see the results of the toString()
method having acted on our funnyGuy
object.
Think about this in a different way. Your funnyGuy
object is like a little kid. It doesn’t have any money of its own to buy big and expensive things. What the funnyGuy
object/kid does have is a parent with a credit card. Having access to this credit card allows the kid to go and buy things that he/she wouldn’t otherwise be able to. The funnyGuy/Object
relationship is very much like a child/parent relationship where if the child doesn’t have something, he/she can check with a parent next.
This entire (very dramatic) interaction is part of what is known as a prototype chain. If an object doesn’t have what you are looking for, the JavaScript engine will navigate to the next object as determined by the [[Prototype]]
property and keep going until it reaches the very end. The very end is when you try to access the [[Prototype]]
property on the Object
itself. You can’t go any further beyond Object
, since that is as basic a type as you can get. I highlight this in the diagrams by having your Object
’s [[Prototype]]
refer to null.
If you’ve ever heard of the term inheritance as it applies to programming before, what you’ve just seen is a simple example of it!
I bet you didn’t imagine that a single line of JavaScript would result in that much explanation, did you? Well, the nice thing is, I front-loaded a lot of conceptual data on you. Hopefully, that makes everything else that you see from here on wards make a lot more sense.
Right now, we still just have an empty object:
var funnyGuy = {};
Let’s specify some properties on it called firstName
and lastName
. As with all things in JavaScript, you have multiple ways of defining properties on an element. The method you’ve seen so far uses the dot notation:
Another approach involves using the square bracket syntax:
The final approach is by extending our object initializer syntax with the literal notation for declaring properties:
There is no right or wrong approach in how you want to specify properties, so be aware of all three variants and go with one that works best for the situation you find yourself in. In general, I use
1. The literal notation when I am specifying properties directly with a value.
2. The dot notation if I am specifying a property whose values are provided as part of an argument or expression.
3. The square bracket notation if the property name itself is something that is part of an argument or expression.
Regardless of which of the three approaches you used for specifying your properties, the end result is that your funnyGuy
object will have these properties (and values) defined on itself:
All of this should be straightforward. Let’s just do one more thing before we move on to bigger and greener pastures. Let’s create a method called getName on funnyGuy
that will return the value of the firstName
and lastName
properties. I will just show what this looks like using the literal notation, for it is easy in the other approaches:
Our getName
property’s value is a function whose body simply returns a string that includes the value of our firstName
and lastName
properties. To call the getName
property, ahem...method, this is all you have to do:
Yep, that’s all there is to declaring an object and setting properties on it. Indirectly, you learned a whole lot about what goes on behind the scenes when a simple object is created. You’ll need all of this fancy learnin’ in the next section when we kick everything up a few notches.
Working with the generic Object
and putting properties on it serves a useful purpose, but its awesomeness fades away really quickly when you are creating many objects that are basically the same thing:
var funnyGuy = {
firstName: "Conan",
lastName: "O'Brien",
getName: function () {
return "Name is: " + this.firstName + " " + this.lastName;
}
};
var theDude = {
firstName: "Jeffrey",
lastName: "Lebowski",
getName: function () {
return "Name is: " + this.firstName + " " + this.lastName;
}
};
var detective = {
firstName: "Adrian",
lastName: "Monk",
getName: function () {
return "Name is: " + this.firstName + " " + this.lastName;
}
};
Currently, if we had to visualize what we have right now, this is what you will see:
There is a lot of duplicated stuff here that is, frankly, unnecessary. Let’s fix that using what we’ve learned about inheritance and the prototype chain.
What we want to do is create an intermediate parent object that contains the properties that are more generic and not necessary to be on the child object itself. From what we have here, the firstName
and lastName
properties are going to be unique for each object we create. Because of that, these two properties still belong on the funnyGuy
, theDude
, and detective
objects.
Our getName
property, though, does not have to be duplicated for each object. This is something we can parcel off into a parent object that the rest of the objects can inherit from. Let’s call this object person
:
Visually, this makes sense. How do we end up creating something like this?
Well, thinking out loud, we need to create our funnyGuy
, theDude
, and detective
objects and ensure the firstName
and lastName
properties are defined on them. That’s easy. Of course, if this is all we did, this wouldn’t be adequate. The prototype for these objects will be Object
, and we don’t want that. We want the person
object with the getName
property to be a part of our prototype chain as the immediate parent. The way we do that is by ensuring the [[Prototype]]
property on funnyGuy
, theDude
, and detective
references person
.
In order to do this, we use the extremely awesome Object.create
method. Let me quickly explain what it does before we see it in action. The Object.create
method, as its name implies, creates a new object. As part of creating the object, it allows you to specify what your newly created object’s prototype will be. Strange how what we want to do and what Object.create
provides are identical! :P
Let’s use Object.create
and the rest of the code that brings the diagram and the explanation you’ve seen to life:
var person = {
getName: function () {
return "Name is " + this.firstName + " " + this.lastName;
}
};
var funnyGuy = Object.create(person);
funnyGuy.firstName = "Conan";
funnyGuy.lastName = "O'Brien";
var theDude = Object.create(person);
theDude.firstName = "Jeffrey";
theDude.lastName = "Lebowski";
var detective = Object.create(person);
detective.firstName = "Adrian";
detective.lastName = "Monk";
Let’s look at all of this code in greater detail. First, we have our person
object:
var person = {
getName: function () {
return "Name is " + this.firstName + " " + this.lastName;
}
};
There is nothing special going on here. We create a new person
object whose type is Object
. It’s [[Prototype]]
property will point you to the Object
type. It contains a method called getName
that returns some string involving this.firstName
and this.lastName
. We’ll come back to the this
keyword and how this works shortly, so keep that one under your hat for now.
After creating our person
object, this is what our world looks like right now:
In the next line, we declare our funnyGuy
variable and initialize it to the object that gets returned by Object.create
:
var funnyGuy = Object.create(person);
Notice that I pass in the person object as an argument to Object.create
. Like I mentioned earlier, what this means is you create a new object with the [[Prototype]]
value pointing to our person
object. This is how things look now:
We created our funnyGuy
object with the person
object set as its prototype object. In the next two lines in our code, I define the firstName
and lastName
properties on the object:
This is your standard, run-of-the-mill property declaration on an object using a name and value. What happens should be of no surprise to you:
We just created our funnyGuy
object and set the firstName
and lastName
properties on it. We just have our theDude
and detective
objects left, and the process for setting the firstName
and lastName
properties is the same, as you can see. Let’s skip looking at them in further detail and move on to shinier and more awesome things instead :P
At this point, if you’ve been following along and understand what is going on, you should be quite impressed with yourself. Many people who work with JavaScript for a very long time have difficulty grasping how inheritance and prototypes tie in to object creation. Wrapping your head around all of this is quite an accomplishment.
However, we are not done yet. Before you pop the champagne bottle and start celebrating, there is one last thing we need to look at before we call it a day.
var person = {
getName: function () {
return "The name is " + this.firstName + " " +
this.lastName;
}
};
When you call getName
, depending on which object you called it from, you’ll see the appropriate name returned. For example, let’s say you do the following:
var funnyGuy = Object.create(person);
funnyGuy.firstName = "Conan";
funnyGuy.lastName = "O'Brien";
alert(funnyGuy.getName());
When you run this, you’ll see a dialog box that looks as follows:
If you look at the getName
method again, there is absolutely no sign of existence of the firstName
or lastName
properties on the person
object. When a property doesn’t exist, I mentioned that we walk down the prototype chain from parent to parent. In this case, that would be Object
:
There is no sign of existence of the firstName
or lastName
properties on Object
either. How is it that this getName
method happens to work and return the right values?
The answer has to do with the this
keyword that precedes firstName
and lastName
. The this
keyword refers to the object that our getName
method is pointing to. That object is, in this case, funnyGuy
:
At the point where the getName
method is evaluated and the firstName
and lastName
properties have to be resolved, the lookup starts at whatever the this
keyword is pointing to. In our case, the this
keyword is pointing to the funnyGuy
object—an object that contains the firstName
and lastName
properties!
Knowing what the this
keyword refers to is something we’ll devote more time to later, but what you’ve seen until now will you get you pretty far.
Tip
Just a quick reminder for those of you reading these words in the print or e-book edition of this book: If you go to www.quepublishing.com and register this book, you can receive free access to an online Web Edition that not only contains the complete text of this book but also features a short, fun interactive quiz to test your understanding of the chapter you just read.
If you’re reading these words in the Web Edition already and want to try your hand at the quiz, then you’re in luck – all you need to do is scroll down!