Another post in the the series that discusses JavaScript for those more familiar with C#. In this episode, the first of a couple on the topic, we look at prototypes and prototypal inheritance.
Despite the fact that the keyword class
is reserved, there are no classes in JavaScript as you'd understand them from C#. And, yet, it is an object-oriented programming language: there are objects, after all. It's all made even more confusing since JavaScript has a new
keyword. How does it new up an object instance, if there are no classes?
In short, instead of using classes as a template from which you can create (instantiate) objects, in JavaScript you use other objects. These objects are known as prototypes, and JavaScript uses what's known as prototypal inheritance, rather than class inheritance. The biggest problem that people have with OOP in JavaScript is that they start off from a class-based model and try and fit that model to JavaScript. Due to the flexibility of JavaScript you can get part of the way there, but it all feels a little contrived.
So, in this series, I want you to forget all about class-based OOP and start over.
We've already seen how to create an object by using the object literal syntax:
var point = { x: 0, y: 0 };
However, the problem with this is that the object is a one-off. What if we needed a whole bunch of point objects? They would all look the same, that is have X and Y coordinates, and possibly have methods like move()
that would translate the point in some direction on the plane, or rotate()
to rotate the point a certain angle around another point, and so on. That is, we need some kind of template that defines the basic data and behavior and then use that template to stamp out point objects. In C# this is what a class does, but how's this done in JavaScript?
Let's take a look. The first thing we need to do is to write a special function known as a constructor. This will construct (that is initialize) a new object.
var Point = function(x, y) { this.x = x; this.y = y; return this; };
But where is the this
variable coming from? In fact, what does this
mean anyway?
this
In C#, this
is easy: it's a reference to the current object. Since the object is an instance of a class and the method is a member of that class, when the method is called we can say with certainty which object this
refers to, and what members it has and so on so forth.
With JavaScript it's not so easy. Or rather it's just as easy: this
is a reference to the object the function was called on.
Here's a point object with a move
method.
var point = { x: 0, y: 0, move: function(x, y) { this.x += x; this.y += y; } };
As you can see, it's declared using an object literal. There's two fields, x
and y
, and the move
method translates the coordinates by the displacement values passed in. It works as you'd expect from a C# viewpoint: because move
is declared within point
, it must always refer to that object.
point.move(1, 2); console.log("Point = (" + point.x + "," + point.y + ")");
And the output is Point = (1,2)
. So here the this
variable is identical to the point
variable.
Now check this out:
var point3d = { x: 0, y: 0, z: 0 }; point3d.move = point.move; point3d.move(1, 2); console.log("Point3D = (" + point3d.x + "," + point3d.y + "," + point3d.z + ")");
Here we're defining a brand new object called point3d
, with three coordinates. We set its move
method equal to point
's move
method. (Remember, functions are objects, they are not glued forevermore to a particular definition like they are with classes in C#.) We then call point3d.move()
. I think you will have worked out by now that this call will not have changed point
but instead will have acted on point3d
. Since the function move
will have been called on point3d
, the this
variable will be referring to point3d
. The output is Point3d = (1,2,0)
.
Now you're comfortable with that, check out this next bit of code.
var spacetime = { x: 0, y: 0, z: 0, t: 0 }; point.move.call(spacetime, 1, 2); console.log("spacetime = (" + spacetime.x + "," + spacetime.y + "," + spacetime.z + "," + spacetime.t + ")");
This is downright weird, so let us take it step by step. We define a new object called spacetime
. We don't define a method on it called move
. Instead we call the point.move
method on it and pass in the usual parameters. The call
method is a method that's defined on all function objects (yes, since functions are objects they can have properties and methods on them just like any other object — this is going to be important in a moment), that takes an extra initial parameter in addition to the function's usual parameters. This initial parameter is an object on which the function gets called. This object therefore become the this
variable inside the function for that call.
So the output is spacetime = (1,2,0,0)
.
Now, you've reread that a couple of times, all you need to remember that this
refers to the object the function was called on. In particular, if you have a function inside another function (and you're already aware of this is how scope works), then the nested function's this
may not be the same as the outer function's this
. If you like, the scope for this
is the function itself and that's it. this
is always a local variable. There's no following the scope chain to try and resolve it.
By the way, if you can't "see" the object at the function's call site, it's going to be the global object, window
.
Looking back at our Point
constructor, you can see that it refers to this
. What is the value of this
here? Well, if the function is called in a normal fashion:
var point = Point(1, 2);
we are going to be clobbering the global object. Why? Firstly, the object that the Point
function is called on is not specified, so it is taken to be the global object, window
. So, inside this call to Point
, this
refers to window
. We'll add two new fields, x
and y
, and set them, and then we'll return this
(which is window
, remember). We then set point
to this return value. Although point
has the required coordinates and so looks like a point object, it's actually window
, and window
will have been altered. Nasty.
Instead we want to call Point
, not as a normal function, but as a constructor. Enter the new
keyword.
var point = new Point(1, 2);
This does what you'd expect: a new empty object is created by JavaScript and then Point
is called on it. The this
variable now refers to the new empty object and the statements in the function will create the new coordinate fields.
The above argument should have drilled something into you: constructors are ordinary functions, but it's the way they're called with the new
keyword that makes them special. This is insane, to put it mildly. If you miss off the new
keyword, you're going to be clobbering the global object and you may not even notice straight away. That's why there's a convention in JavaScript to name constructors with an initial capital letter: it's a hint to the reader that this particular function must only be called with new
. Unfortunately, there's no keyword to mark a function as a constructor, nor is there some compiler/interpreter option to flag misuses for us.
Even better, there's no need to have the return
statement in a constructor as I have in the Point
constructor above. If there is no such statement, JavaScript will return this
for you. This is yet another difference between constructors and functions: if you don't return anything from an ordinary function, undefined
is returned for you.
If we wanted to add the move
method to our constructor, we may be tempted to do this:
var Point = function(x, y) { this.x = x; this.y = y; this.move = function(x, y) { this.x += x; this.y += y; }; return this; };
This would work — we'd get point objects with a move
method — but it contains an inefficiency. We are in effect declaring a move
method for every single point object we create with the constructor, which is enormously wasteful of memory (remember the function is not compiled but interpreted every time it is called, so we are in effect copying the same source code over and over again).
We'd prefer to have the method shared, that is, declared on the template for the point objects. So far, we don't have a template, per se, just a special function that knows how to construct point objects. Enter the prototype object.
The prototype object is the template we need. It is shared amongst all objects created from it, so if there is a method declared on the prototype object it will be visible to all such objects. But where is this prototype object declared, and how can we access it?
Every function object has a property called prototype
. Normally it's an empty object and is usually ignored, but it gains a special significance for constructors. A constructor's prototype
is the template from which the new
keyword creates the new object before calling the constructor. In my discussion above, I glossed over this and said that the new
keyword would create a new empty object. Not quite; instead it creates a new object that looks like the prototype.
Let's investigate.
var Point = function(x, y) { this.x = x; this.y = y; return this; }; Point.prototype.move = function(x, y) { this.x += x; this.y += y; };
What we have here is our original Point
constructor definition, and then a statement that creates a property called move
on the Point.prototype
object. This method does the usual translation thing. We didn't need to declare prototype
, since JavaScript creates one automatically for every single function. Now let's call it:
var point = new Point(1, 2); console.log("Point = (" + point.x + "," + point.y + ")"); point.move(3, 4); console.log("Point = (" + point.x + "," + point.y + ")");
Here we're creating a new point object and logging its value. We then call the move
method on point
. Unfortunately, point
does not have a move
method. On discovering that, JavaScript will then check the prototype
for the method.
How does it find the prototype
? Luckily for us, the new
keyword has set up a hidden property for us in the new object called constructor
that references the constructor function that was used to create the object; in our case, Point
. So, a new object gets an extra property called constructor
that points to the constructor function, and as we've seen the constructor function has a property called prototype
.
So, if the method is not found in the object, JavaScript will go take a look at the prototype object to see it it has the method, instead. If so, it calls it. If it hasn't, JavaScript will check to see if the prototype object was constructed and go find it's prototype object to see if it has such a method. And so on, up the prototype chain.
Yes, you've guessed it: this is JavaScript's inheritance. Constructed objects inherit their behavior from their prototypes. Even better, given the description of how a method is called on an object, we can see that if I declare a move
method on a newly created point
object, it will get called in preference to the prototype object's. In fact, it hides the prototype's version. That's usually known as polymorphism and we're overriding a method.
Welcome to prototypal inheritance and polymorphism, all without a class in sight.
Oh, by the way, remember the call
method above that we used to set an explicit this
variable on calling a function? It's a method that's defined on all functions; or, translating into what we now know, it's a method that's defined on a function's prototype
. The constructor for a function object is Function
.
We'll continue with this topic next time.
Now playing:
Power Station - Taxman
(from Living in Fear)
2 Responses
#1 Dew Drop - February 24, 2009 | Alvin Ashcraft's Morning Dew said...
24-Feb-09 6:22 AMPingback from Dew Drop - February 24, 2009 | Alvin Ashcraft's Morning Dew
#2 JavaScript for C# programmers: prototypes and privacy said...
27-Feb-09 10:17 PMContinuing my series about learning JavaScript when you're a C# programmer, using Firebug in Firebox as our testing ground. In this episode, overriding, privacy, and class models. Last time we saw how to create inheritance from JavaScript's constructors
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response