Inheritance in JScript - Part 1

Inheritance in JScript - Part 1

  • Comments 2

JScript (JavaScript / ECMAScript) is not a class-based language like C++ or Java; rather it is a prototype-based language. Nevertheless, it is often desirable to use class-based design patterns (such as inheritance) in ECMAScript programs, especially as they get larger and more complex.

In the next few articles, I'll show how I do inheritance in my iHD code for HD DVD; I'm not claiming this is the best (or even the only) way to do it, but it works for me and has most of the desirable features of "traditional" class-based inheritance. I'll use the terms JScript, JavaScript, and ECMAScript interchangeably in this series mostly to help with search-engine results :-) but also because this applies to all three. I'll also probably get lazy and refer to constructors as classes and other things like that. This is not intended to be a normative reference...

One trap to avoid is to trying to fit a square peg into a round hole -- JavaScript is best suited to small projects, and it wasn't really meant to support complex class hierarchies. Don't try to turn your web page scripts into complicated systems of objects with many layers of abstraction if all they are doing is displaying menus or showing popup dialogs. Keep it simple. The larger and more complicated (and, generally, more abstract) your code becomes, the harder it is to debug and the greater the chance that you'll have subtle errors that take a long time to track down. With great power comes great responsibility.

Having said that, sometimes your code really can benefit from some degree of abstraction and some degree of class-based code re-use, and that's where this series will hopefully come in handy. Note that I will not be explaining how prototype-based inheritance works, or anything else that is just part-and-parcel of JavaScript programming; if readers really want to know, I might do so in the future, but for now I assume you know how it works. That said, I will be fairly liberal with my commentary becomes sometimes people know that something works, but not really how or why it works :-).

Part 1: Simple Inheritance

Here's some basic code that we'll look at (and expand upon) in the series. The code itself is not commented (to make it shorter :-) ) but I'll discuss each snippet in detail below.

//////////////////////////////////////////////////

function Thing(id)
{
  this
._id = id;
}

Thing.prototype._className = "Thing";

Thing.prototype.getID = function Thing_getID()
{
  return this
._id;
};

var thing = new Thing('thing');
print("my id is "
+ thing.getID());
print("---"
);

//////////////////////////////////////////////////

function Point(id, x, y)
{
  this
._id = id;
  this
._x = x;
  this
._y = y;
}

Point.prototype = new Thing();
Point.prototype._className = "Point"
;
Point.prototype.constructor = Point;

Point.prototype.getLocation = function Point_getLocation()
{
  return "(" + this._x + "," + this._y + ")"
;
};

var point = new Point('point', 0, 0);
print("my id is "
+ point.getID());
print("my location is "
+ point.getLocation());

//////////////////////////////////////////////////

function print(s)
{
 
// Replace with whatever is approriate
 
// for your environment (HTML, ASP, HD DVD, etc.)
 
WScript.Echo(String(s));
}

Examining the Code

Thing

Let's take a look at the Thing code. Thing is the base class from which all others will derive in this series. It is very basic, supporting only and _id property (accessible via the getID method).

function Thing(id)
{
 
this
._id = id;
}

So far, this looks pretty standard. We create a function Thing that we intend to use as a constructor. It takes a single parameter, id, that we store as the _id property on the this object. A convention I like to use is that all non-public members begin with an underscore; since there is no information hiding in JScript (ie, no private or protected modifiers) anyone can get at any properties of any object. To help discourage random access to properties, they are prefixed with an underscore if only the class itself should directly access them.

Note that some people like to implement "private" members by using closures; I personally don't like this approach because it makes reading and writing the code harder, it makes debugging harder, and it is very inefficient. Maybe one day I'll show an example of this style of coding to prove the correctness of my opinion :-)

Thing.prototype._className = "Thing";

Here's the first debugging aid in the code -- the _className property. I put it on all my objects so that when I'm debugging I can just look at this._className and know exactly what kind of object I'm looking at. (In fact, I tend to leave it in the Watch window in Visual Studio so it's always available). In proper class-based languages (like C# or even JScript .NET), the debugger usually provides this type information for you, but since to ECMAScript everything is just an Object you don't get any help from the tools. Nevertheless, Visual Studio helps those who help themselves, so you help yourself. Personally I never rely on _className for anything other than help during debugging; that is, I never have code that uses the value. That way I can remove it later on if I like.

Thing.prototype.getID = function Thing_getID()
{
  return this._id;
};

Here's a basic accessor method to get the _id property. As noted above, I have a convention that only the class itself should access members with an underscore in their name, so you need accessors for "outsiders" to get the info. For something like the ID (which is unlikely to change during the life of the object, nor is it likely to ever require any class-specific behaviour), you might be able to get away with direct access. But with pretty much any other property, sooner or later you're going to decide you need to compute it (rather than just returning a simple value) or you're going to want to have different objects up the class hierarchy over-ride the behaviour. For this reason, it's best to make all your accessors into methods so you can change them in the future without having to update all your code.

Another debugging tip -- give all your functions names! ECMAScript supports anonymous functions, and that's what you would get if you did something like:

Thing.prototype.getID = function() { bla bla bla }

But the problem with that is the debugger has no idea what the name of your function is -- even though you can call it in code as foo.getID(), that is just a reference to the function object. The function itself has no name, so when you are debugging and you try to look at the call stack, all you will see is a long list of JScript Anonymous Function stack frames. Not very useful at all. By giving all your functions names, you get a much better debugging experience.

Another convention you can take or leave: property accessors are written in camel-case (the first word is lower-case, but subsequent words are Proper Case) whereas functions and methods that actually "do something" are in Proper Case. So, for example, we have foo.getID() (or maybe foo.getId() -- acronyms are always a fly in the ointment) but CreateWidget() or DoSomethingReallyCleverNow().

var thing = new Thing('thing');
print("my id is " + thing.getID());
print("---");

Just a bit of code to show our handywork in action. It's hardly rocket science, and will print out "my id is thing".

Point

The Point class is also very simple, and represents a basic (x, y) co-ordinate in 2D space. It derives from Thing so that it gets all the wonderous benefits of having a getID method. Code re-use at its finest, boys and girls.

function Point(id, x, y)
{
  this._id = id;
  this._x = x;
  this._y = y;
}

Nothing too fancy here; another basic constructor taking three arguments -- id, x, and y -- and assigning them to member properties following the underscore-means-private convention. Note that here we don't try to re-use any of the Thing initialisation code; it's pretty simple, so we just copy-and-paste the assignment to make sure we have an _id property on all Point objects, just like the Thing prototype method is expecting.

Like I said, code re-use at its finest. But trust me -- things will improve.

Point.prototype = new Thing();
Point.prototype._className = "Point";
Point.prototype.constructor = Point;

Here's where we set up the inheritance. The first line should be familiar to anyone who's ever done this before -- we make sure that all Point objects get access to all of the Thing members through their internal [[Prototype]] property (that double-backet notation is ECMA262-speak, by the way; maybe I'll talk about that one day, too, if people like). The second line is the same debugging-help line from before. We'll know a Point object when we see one in the debugger due to its _className property. But the third line might be something you haven't seen before...

Whenever you create a Function object (ie, whenever you write function foo() {...}) JScript automatically creates an empty prototype object and gives it a constructor property that points back to the Function object you're creating. So, for example, when we created the Thing function, JScript did something like this (Section 13.2 of ECMA 262):

  1. Create a new object, F
  2. Fiddle around with F so it it looks like a function
  3. Create another object, P
  4. Add a constructor property to P, and set it to F
  5. Add a prototype property to F, and set it to P

That's right -- the function and its prototype have mutual references to each other. Quite why this is useful may not be apparent right now, but we'll get to it later. Much later. But what happens when we go and obliterate the prototype property of Point with a new Thing?

  1. Create a new Thing object, T
  2. Set the prototype property of F to T

Now what? We blew away the constructor property that JavaScript so helpfully gave us, and the instance of Thing (T) that we replaced it with doesn't have a constructor property. So poor old Point objects are left out in the cold with nary a constructor property to call their own. Don't dwell on this tragedy too long though, because the third line of code remedies that problem by repeating Step #4.

Point.prototype.getLocation = function Point_getLocation()
{
  return "(" + this._x + "," + this._y + ")";
};

Here's another pretty simple setup. We add a getLocation method to all Point objects. It's simply going to return a string representation of the x and y co-ordinates. Now you'll note that I break my own guideline not two-dozen paragraphs after telling you about it -- I should really have accessor functions for _x and _y, as well, in case I need to compute them later on. But continuining in the long-standing tradition of "Do as I say, not as I do", I'll hand-waive and say it was in the interests of brevity. Feel free to update the code with getX() and getY() if you see fit.

var point = new Point('point', 0, 0);
print("my id is " + point.getID());
print("my location is " + point.getLocation());

Some more existentialist code to prove to myself that the last several hours of blog writing haven't been totally in vain. It will print out the amazingly original text "my id is point" and then "my location is (0,0)" proving that not only does the getLocation() method work as advertised, but that we got access to the getID() method, like, totally for free, dude!

function print(s)
{
  // Replace with whatever is approriate
  // for your environment (HTML, ASP, HD DVD, etc.)
  WScript.Echo(String(s));
}

You need some way to see the output; I was using WSH but you can use whataver you like.

OK, that was underwhelming.

It will get better.

You can quote me on that.

  • Last time we took a (very) long-winded look at a simple JavaScript program that used basic inheritance....
  • Thanks. Keep up the series.
Page 1 of 1 (2 items)