Fork me on GitHub

Loading...

comments powered by Disqus

Transcript

If you hang around the JavaScript world long enough, you’ll come across a bunch of different recipes for object-oriented programming. The standard way, so much as there can be a standard way, is this.

And this is the recipe for inheritance.

That’s the standard. But why this? Why this… mess? And why does it work?

Hi, everyone. My name is James Shore and this is Let’s Code: Test-Driven JavaScript. I’m here with my Lessons Learned on object-oriented programming in JavaScript. I’m recording this on June 1st, 2013.

In today’s episode, we’re going to build up these recipes from first principles. We’ll start with a review of object fundamentals, then look at how functions work in JavaScript. Next, we’ll cover prototypes and inheritance, then polymorphism and method overriding. We’ll discuss classes and instantiation in the prototypal model, then see how these concepts map to the classical model most people use. Finally, we’ll delve into the inner workings of the instanceof keyword, before closing with a look at future directions, a tool for exploring these concepts further, and my recommendations.

This is a big topic and we’re going to cover a lot of ground. Be sure to take advantage of the pause and rewind buttons in your video player. You can use the timestamps here to jump to sections of interest.

1. Object Fundamentals

Let’s start with the basics. These are the common JavaScript types—these are everything you can actually type into your source code. However, most of these are not actually primitive types. The only things that are really primitives are strings, numbers, and booleans; a couple of ways of saying “can’t find it,” and objects. Everything else—functions, arrays, and regular expressions, as well as all the other objects you use in your program—those are all variant kinds of objects.

So, what is an object? Well, it is a set of name/value pairs. In other languages, you might call it a dictionary, or a hash, or maybe an associative array, but fundamentally, it’s key/value pairs. You can use any number of keys with any name, as long as it’s a string, and each string can be associated with any value. Those values can be of any type: any of our basic primitive types, as well as any object type, including functions, and of course, objects themselves.

An important thing to note about objects is that, although primitives are passed by value, objects are passed by reference. Let’s see what that means.

So if we have two variables, named number1 and number2, and we assign a value to number1—let’s say 3.14 etcetera—and then we copy that variable into number2, those two values are not connected. So if we change number2, number1 is unaffected.

Objects, on the other hand, are stored by reference. What that means is that if you assign an object to one of these variables and then we copy that object into a new variable, we’re not copying the object. There’s still only one object. What we’re doing is copying the reference. The pointer. The arrow. So if we change object2, object1 gets changed as well. object2.a is 42, but also, object1.a is 42.

One last thing before we’re done with the basics.

If you ask for a property that isn’t found in an object, you’ll get undefined as your result back. Now, you can assign undefined to any property you want. That doesn’t actually delete the property. If for some reason you want to get the property out of the object entirely, you have to use the delete keyword. Now honestly, in practice, the distinction is usually not that important.

2. Functions & Methods

I mentioned earlier that functions are just regular kinds of objects, and that’s true. When you define a function, JavaScript creates an object for you that has three properties already defined: the name, which is the name of the function; the length, which is the number of arguments; and a prototype, which I’ll explain later.

Because functions are just regular objects, you can do everything with them that you can do with a normal object. You can assign properties to them; you can assign them to different variables; and when you do, they’re passed by reference, so you can run that function from its new location by just saying the variable name and parentheses.

When you put a function inside of an object, it’s typically called a “method.” You can run methods just like you run any other function by saying object.methodname and then parentheses.

When you do that, JavaScript will set the this keyword to the object that you used. So if you say myObject.get(), this will be set to myObject. Then when the function returns, this will be set back to whatever value it had before.

The this keyword is JavaScript’s biggest gotcha. It depends on the object, not where the function was defined, and this can cause a lot of problems.

So, if we have myMethod which returns this.val, and we call object1.get(), we’ll get a 42 back. If we call object2.get(), this will be set to object2 and we’ll get 3.14159 back. If we just call myMethod() directly, this won’t be set to anything in particular. It could be undefined if we’re using strict mode; it could be the global object; it’s really hard to tell for sure.

So you have to be really careful when you’re using this. If possible, I really recommend that use strict mode whenever you can because that will force this to be undefined. It won’t prevent mistakes relating to this, but it will help you catch them more quickly.

If you have a case where you need this to be a particular value, you can force it by using either call(), apply(), or bind(). Now the details of those functions are outside the scope of this episode, but let me show you an example of one of them.

If we say myMethod.call(), then that will run the myMethod function and force the this value to whatever we said. So myMethod.call(object1) will set this to object1.

3. Prototypal Inheritance

Okay, that’s the basics. Let’s get into some of the more complicated stuff.

It’s pretty rare that you’re going to be defining all of your object from scratch. You’ll typically have some sort of repeated pattern. For example, in this case, object1, object2, and object3 are all using the same function. And rather than define them all separately like that, which would be a maintenance nightmare in the long run, what you can do is use something called “prototypes.”

The way this works is that you can define a single object, and then have other objects inherit from it, or “extend” it. The way you do that is by calling Object.create().

So if I have a parent object with a function and a value, I can create a new object (which I’ve called child) by saying Object.create(child). You can do anything with this child object that you do with any other object. You can add values to it or even extend it with another child.

Now the cool part is what happens when we start to use these objects.

The base object is just like before. Let’s say we say parent.get(). Well, this is going to be set to parent, and then JavaScript will look for get on that object. And when it finds it, it’ll call the function, which will say return this.val, which will cause JavaScript to look for val on parent. So that’s pretty normal.

But here’s the cool part. If we call child.get(), then this is set to child. JavaScript will look for get on child, but it won’t find it. So it will look at the prototype—it will go up the prototype chain—and look at parent, and it will find get there. When it finds that function, it will try to return this.val, which means it will go back to child, look for val, and find it. So child.get() will return 3.14 rather than 42, even though it’s using the function that was defined in parent.

JavaScript will go as far up the prototype chain as it needs to go in order to find a property. So if we said grandchild.get(), then JavaScript will look for get on the grandchild. It won’t find it, so it will go up to the prototype and look for get on child. It won’t find it there, so it will go to parent, look for get there, and find it. It will call the function, try to return this.val, so again it will go back to grandchild, look for val. It won’t find it, so it will go up to child, look for val, find it there, and return 3.14159.

This is the fundamentals of inheritance in JavaScript. Now, if you’ve seen other ways of talking about objects in JavaScript, they might have focused on classes or something else first. But this right here—this prototype-based inheritance—is fundamentally how JavaScript works. Actually, JavaScript doesn’t have any other form of inheritance other than this prototypal inheritance I’m showing you here.

There’s one more wrinkle I want to share, and that is that every single object has a prototype, except for the base object, and any objects you create yourself to explicitly not have prototypes. Here’s what they look like.

By default, objects that you create have Object.prototype as their prototype, and functions have Function.prototype as their prototype. Notice that this is where those call(), apply(), and bind() methods I was talking about earlier come from.

Now, this is way too much detail to show in most of these visualizations, so what I’m going to do is just use [[Object]] and [[Function]] to refer to those prototypes from here on out.

4. Polymorphism & Method Overriding

Once you have objects in a prototype chain, you might find that you want children to behave differently from their parent, even when the same method name is called. This is called “polymorphism.” Polymorphism means, “the same name, but different behaviors.” Now, technically, you can have polymorphism without inheritance, but we’re not going to get into that right now.

It’s easy to do polymorphism in JavaScript; you just use the same property name, but assign a different method. So if we wanted to have a firmAnswer object that answered something differently—or answered in a different way—we’d just say firmAnswer.get and assign the function. In this case, what we’re going to do is we’re going to return the same value and return “!!” at the end.

So if we say answer.get(), that will give us “42” back, but if we say firmAnswer.get(), that will say “42!!” back. Anyway, you get the idea.

This relationship is actually a little easier to see if we don’t have the function visualizations in there, so I’m going to stop showing them for now.

Now you’ll notice here that our fn2 is looking at this.val and our fn1 is looking at this.val. That’s a bit of duplicated logic. It’s not too bad here, but in more complicated programs, you find that that sort of logic is very difficult to maintain. So typically, what we’re going to want to do, is call fn1 from fn2.

Unfortunately, that’s not quite as easy as it might seem. The obvious answer is to just call fn1… to just say answer.get(). That doesn’t work. It returns the wrong answer. It’s going to return 42. Do you know why? If you don’t, pause the video for a moment and see if you can figure it out.

Okay, here’s why.

When we call firmAnswer.get(), this is set to firmAnswer and JavaScript looks for the get property. It finds it and it runs fn2. That runs answer.get(), which is going to set this to answer, and then look for the get property on answer. When it finds it, it will run fn1 and try to return this.val. But because this is set to answer, when it looks for this.val, it will find it on the answer object and return 42 rather than 3.14159, which is what we expect.

So, in order for this to work properly, we need to use the call function. We need to say answer.get.call(this). See if you can figure out why this would work.

Okay, here’s how it works.

When we call firmAnswer.get(), this is set to firmAnswer… it looks for get… finds it… runs fn2… and now it says answer.get.call(this). That sets this to, well, the same this again. But then it runs answer.get() directly, which tries to return this.val, which looks for val on firmAnswer, finds it, and returns the correct answer.

5. Classes & Instantiation

You can organize your JavaScript objects any way you like, but a really common way of organizing it is to separate your methods from your data.

For example, we have this answer object which, when you ask it to get the value, returns the value that it has stored.

Well, it’s pretty typical to want to have multiple copies of this object, so typically what people will do is they will put the function in a prototype—which we’ll call AnswerPrototype—and then they’ll have multiple objects extend that prototype to give us special values.

So we see here that lifeAnswer has the value of 42, because it’s the answer to Life, the Universe, and Everything, and dessertAnswer has the value of pi, for, well, obvious reasons.

If you want to specialize that answer, as we did with firmAnswer before, you can do the same thing. We have our FirmAnswerPrototype add its fn2—the one that puts “!!” on the end—that extends AnswerPrototype. And then we have our luckyAnswer and magicAnswer extend that.

When you use this approach to organize your objects, the prototypes are typically called “classes,” and the objects—the objects that extend them—are typically called “instances.” A class that extends another class is called a “subclass.”

Creating an instance is called “instantiation,” and you’ll notice that there’s a two-step process for that. First, you create the object by extending the prototype, and then second, you initialize its data. (Remember, instances are usually about the data and the prototypes are about the methods—the classes are about the methods.) So we extend and then we initialize.

The problem with this is that this initialization logic is duplicated every time we create a new object. In this simple example, it’s not such as big deal, but in real programs, the initialization logic is usually pretty complicated. So we don’t want to duplicate it everywhere. That would be a big maintenance problem.

It also violates encapsulation. One of the nice things about object-oriented programming is that it allows you to decide how your data is going to be stored in a way that nobody else has to worry about. You just provide access to that data through your methods and then, if you want to change the way data is stored, then you just do it. You update your methods—your object’s methods—but you don’t have to update all the rest of the program.

But here, because all our instances are accessing val directly, we can’t change the way val is stored without having to change all of our instances.

So what’s really common is to use some sort of initialization function. In this case, I’m going to call it constructor(). This is a common method that is used to initialize your objects.

Here’s how this works. Let’s say that we want to create a new instance for magicAnswer. We’ll extend FirmAnswerPrototype and then we’ll say magicAnswer.constructor(3). That will set this to magicAnswer and it will look for the constructor property. It won’t find it on magicAnswer, so it will look at the prototype. It won’t find it there, so it will look at the prototype of FirmAnswerPrototype. It will find constructor there and then run fn0(value). fn0 is going to set this._val to value. So it goes to this, sets the value, and there we go.

Note that I’ve changed _val to have an underscore in front. This is a common convention in JavaScript to say that a property is private. In other words, you shouldn’t access or change this property. Now, nothing in JavaScript is going to enforce that it doesn’t get changed, but that’s the polite thing to do. And if you follow that rule, that means we can change AnswerPrototype without breaking any of the rest of our code: without changing the way FirmAnswerPrototype has to be programmed or any of our instances.

So that is a complete view of prototypal inheritance in JavaScript. It is a little bit different than what I showed you at the beginning, though, so we’re not quite done yet. But you could do all object-oriented programming in JavaScript using this model.

6. The Classical Model

Now, let’s look at the classical model. This is going to build on everything we’ve done up until now.

To start with, remember how, in the prototypal model, we instantiate an object by creating the object and then running a constructor of some sort? That’s so common that JavaScript has a special keyword just for that. It’s called new.

However, new is a little bit, well, weird. It doesn’t work the way we’ve seen up until now, and that’s what makes the classical model I’m about to show you different from the prototypal model I’ve been showing you up until now.

Now, before we get into that, I have to show something a little bit strange about functions. Remember that prototype property I said I’d explain later? Well, I’m going to explain it now. And it is… it’s weird.

When you define a function, JavaScript creates an object with name, length, and prototype properties. That prototype property actually points to a whole ’nother new object with a constructor property that points back to the function you’ve just created. So whenever you create a function in JavaScript, you’re actually creating two objects: the function object, and then this prototype object that’s just hanging out there.

I told you it was weird.

Now look at this closely. Does this look familiar?

Yeah. It’s a prototype. It is, basically, a class. So every time you define a function in JavaScript, you’re actually defining a constructor that’s associated with a little do-nothing class.

Now, of course, not every function you define is meant to be a constructor. So the common convention in JavaScript is that, if it’s meant to be a constructor, it starts with a capital letter, and if it’s meant to be just a normal function, then you start it with a lower-case letter.

Okay, so that’s a little weirdness of functions. Let’s see how this plays out to create the classical model.

First, let’s go back to our prototypal model. We’ll walk through it step by step.

So what we’re going to do is we’re going to create a class called AnswerPrototype. We’re going to create a constructor in it. And when we create that constructor, JavaScript is going to define a function object and a prototype object (which we don’t care about; we’re just going to ignore it). We’re also going to create a get function on our AnswerPrototype and we’re going to create a couple of instances: lifeAnswer and dessertAnswer.

So that’s a basic example using the prototypal model. Now let’s do the exact same thing, but this time using the classical model and the new keyword.

In the classical model, we define the constructor first. So we’ll create this Answer function. JavaScript will automatically create an object to go along with it with a constructor property that points back to our Answer function. That prototype is our class. It’s going to fulfill the exact same purpose that AnswerPrototype fills in the top example. So we’ll set our get method on AnswerPrototype. Then we can instantiate it by calling new Answer(). We’ll say new Answer(42) and that gives us the right value. That’s going to create a child of Answer.prototype and initialize it by calling the Answer constructor with the value of 42.

The way it knows to create lifeAnswer with Answer.prototype as its prototype is because JavaScript looks at the prototype property of the constructor when you use the new keyword.

The same thing happens with dessertAnswer.

That’s the classical model.

Now, it gets a little more complicated when we start dealing with subclasses. So let’s take a look at how this would work with a subclass. Let’s add the FirmAnswer class that we looked at in our previous prototypal example.

Again, we’ll start out by creating a FirmAnswer constructor first. JavaScript will automatically create the FirmAnswer.prototype, but this one we can’t use because we need our FirmAnswer.prototype to extend Answer.prototype—and it doesn’t. So what we’ll do is, we’ll set FirmAnswer.prototype to a new object that we’ll create by extending Answer.prototype. That will cause the old FirmAnswer.prototype to have nothing pointing to it, so it will get collected by the garbage collector.

Next, we’ll set the constructor property to point back to FirmAnswer. Now, as far as I can tell, this constructor property is not necessary. Everything that’s built into JavaScript will work just fine without it. But we’re going to set it here to be consistent. You could probably get away with not using it, but I have to admit, I haven’t been brave enough to try that, so remove it at your own risk.

All right, so we have FirmAnswer.prototype with a constructor. Now we need to set our get method on it. Then we can instantiate it as normal. we’ll say new FirmAnswer(7). That will create our luckyAnswer extending FirmAnswer.prototype and we can do the same thing with magicAnswer.

Here’s a side-by-side comparison of the two models. I’ve numbered each of the sections so they correspond. So number one in the prototypal model is doing the exact same thing as in the classical model. Go ahead and pause the video here if you’d like to study this further.

7. instanceof

There’s one more detail I’d like to share with you.

It’s often convenient to know which class was used to instantiate an object. To achieve that purpose, JavaScript has a keyword: instanceof. The way this works is it looks at the prototype of the constructor and compares it to the object. This is actually a little hard to understand unless you see it, so let me just show it to you.

We can ask if lifeAnswer is an instance of Answer. And intuitively, we know that’s true. But how does JavaScript know? Well, here’s what happens:

First, it looks at Answer.prototype. Not Answer’s prototype, but Answer’s prototype property. Then it looks at lifeAnswer’s actual prototype. And if those two things are the same object then, yes, it’s an instance.

So lifeAnswer wouldn’t be an instance of FirmAnswer. FirmAnswer.prototype is over here. lifeAnswer’s actual prototype is up here. They don’t match, so it’s not an instanceof.

However, there is one caveat to this—one exception to the rule. luckyAnswer is an instance of Answer because JavaScript will go up the prototype chain. So JavaScript will look at Answer.prototype. Then it will look at luckyAnswer’s prototype. That’s not a match, but it will go up the prototype chain and look at Answer.prototype as well. That is a match, so luckyAnswer is an instance of Answer.

8. Future Directions

That’s everything you need to know in order to understand how inheritance works in JavaScript. I do want to share a few more things with you, though.

First, in the upcoming version of JavaScript, as defined in the EcmaScript 6 specification, there’s going to be a new syntax which makes it easier to do the classical model of inheritance in JavaScript. It’s the class syntax, and I’ve got it shown here on the left.

Now, as far as I can tell in my research, that yields the exact same underlying object model that classical inheritance does, as I’ve shown here on the right. But it’s going to be much easier to work with.

If you want to see how that compares to the classical model, I have a side-by-side comparison here. Go ahead and pause the video if you want to study this in depth.

9. The Definitive Guide

I’ve called this episode “The Definitive Guide to Object-Oriented JavaScript,” which was perhaps a little presumptuous of me. It isn’t possible for single tutorial, no matter how long—and I didn’t really want it to be that long—anyway, it’s not possible for a single tutorial to cover absolutely everything there is to know about JavaScript. There’s some stuff I’ve intentionally left out, such as getters and setters, static variables, much more… the details of bind and apply, for example…

What makes this the definitive guide is the website I’ve created to accompany this video. This site is an object visualizer. So what you can do is type in JavaScript and it will analyze what you type and display the object map on the screen. So if we wanted to see what a function did, we could just add it here. We could “Show all functions” and see how that function is turned into objects.

This is a very cool tool. It’s a lot of fun; it’s got a lot of default examples for you to play with. Check it out. This is the best way to really understand how inheritance works in JavaScript and how objects work in JavaScript. Test your understanding by typing in some code here, clicking the evaluate button, and see what comes out. Play with your own ways of making inheritance structures; try your own abstractions; try other people’s abstractions. (I’ve got John Resig’s in here, for example, but you can do much, much more.)

Anyway, this is the real definitive resource because it actually runs in your real browser. So it will tell you whatever your browser—it will tell you the JavaScript model used by your actual browser. Check it out.

10. Recommendations

To close, here are my recommendations for object-oriented JavaScript in your programs.

First, use the classical model. I know it’s not very popular, and it’s kind of weird, but it is the standard. Everybody understands it. If you use the classical model, anybody who has the slightest familiarity with object-oriented JavaScript will understand this approach. That can’t be said about any other approach you use.

It also is the one that’s most likely to have good IDE support if you want to do some sort of automatic syntax checking or refactoring in your IDE, and it’s also the only approach that supports instanceof. You can hack in support for instanceof into the prototypal model, but it’s not very convenient and actually ends up being uglier than the classical model.

Second, "use strict";. Use the strict mode. It will help prevent situations where this isn’t defined properly.

Third, use JSHint or some other linter. It will help you catch cases where you forgot to use new or you used it in the wrong spot, as long as you use the convention where a class name starts with a capital letter.

Fourth, when the EcmaScript 6 class syntax becomes available, go ahead and use it. It might be a while: at the time I record this, it’s not available in any of the popular browsers, and that means it’s going to be a long time before it’s available in IE. But when it is available, go ahead and use it. It’s a nice, elegant syntax. It maps down to the regular classical model, which means it’ll play nice with all your existing stuff.

And finally, experiment with Object Playground. I created that site specifically to be a way to learn more about object-oriented JavaScript. You’d be surprised what you can find out about it. For example, go ahead and see whether or not you need that constructor property. What happens when you take it out? Can you find any case where it’s needed, or any case where it’s not needed?

So that’s what I’ve learned about object-oriented programming in JavaScript. Thanks for watching, everybody, and I’ll catch you next time!