Examining the Scriptaclous Unit Testing Implementation

2: Prototypes, Constructors, and "The New Way"

Javascript does not (yet) implement OOP with classes. It uses a prototype system, which is to say it allows you to describe a template, or prototype, in the constructor function. Then, any objects created with that constructor get their own copies of the data described in the prototype (that means methods and members--it's all data in JS). The prototype object allows you to speak of something like Test.Unit.Runner as a class, although the literature I've read would call it and other "classes" created this way pseudoclasses. So I imagine the next step is to look for the constructor for this pseudoclass, and that will take me into unittest.js.

Here is the first mention of Test.Unit.Runner:

Test.Unit.Runner = Class.create();

Uh oh. The right-hand value here doesn't look like a function definition. It's a method execution. What's this "Class" business? As someone burdened with Java experience, my first notion is that a method is being called on the global Class object, which models classes in the language. But, JS doesn't have classes. This must be a construct, another pseudoclass that the code's authors have written for their convenience. Where will we find that?

In our new old friend prototype.js:

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

Whoa. So "Class" isn't even a pseudoclass. It's just a base object created in prototype.js, with one piece of data, a method (at least so far it only has one method. In JS, you can add more stuff to objects anytime you want).

If you're new to coding and reading JavaScript "The New Way", this is going to look funny to you. "Brackets, a colon, and no equal signs anywhere in the body of the declaration?" you ask. And what's with all these function() {...} structures? People aren't only using JS the way they use C or Java or Perl, that is in a primarily imperative way. There's a lot more functional programming in JS these days, and it's taking advantage of the strong influence of Scheme on JS.

I'm no expert at this stuff. I'm only halfway through the Little Schemer as I type this. But here's my interpretation. If you know this stuff, you could skip over it, but I'd be grateful if you gave it a look and told me what I was getting wrong.

The outer brackets contain the definition of the object "Class". Classes in JS are just collections of labeled data, and functions are just data, as are strings, numbers, and other objects. So you can define an object is this hash-like way: label: value. In fact, there's a whole data format called JSON that takes advantage of this to offer you a simpler way to serialize data structures.

So, the one "member" of Class is called create. What is it? It's a function. The parens after the word function tell you that this function called create doesn't need any arguments. Then, the contents of the following brackets give you the body of this function called create. What's it do?

Well, it returns a function. Again, this new function doesn't need arguments. It's body follows in brackets. It contains one line of code: this.initialize.apply(this, arguments);

If your mind is starting to feel a bit stretched, don't worry. I'm right with you. Let's just enjoy the burn. We're learning something! I'll examine this line of code, but first I want to back up a bit, to my earlier assertion that the code new Test.Unit.Runner(...) in string_test.html is calling a constructor and producing an object. The constructor is a function. Where is our constructor? It's inside the definition of Class's create member.

Let's rewrite some of this sophisticated code to make it clearer. Here's another way to express what Class.create is:

Class.create = function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }

But unittest.js doesn't set Test.Unit.Runner equal to Class.create, but to Class.create(). This line of code is calling Class.create, and the return value of the function is what's assigned to Test.Unit.Runner. What does "Class.create()" return?

function() {
  this.initialize.apply(this, arguments);
}

So...

  
Test.Unit.Runner = function() {
  this.initialize.apply(this, arguments);
}

Or, if we rewrote the code just a bit:

    
function TestUnitRunner() {
  this.initialize.apply(this, arguments);
}

There's our constructor. OK. So, what's it do? This is a little tricky too.