Examining the Scriptaclous Unit Testing Implementation
3: The Constructor in Action
We just found the constructor for Test.Unit.Runner
: It's the inner function returned by
function() { return function() { this.initialize.apply(this, arguments); } }
Let's take the constructor from the inside out. The left-most piece is a call to a function called apply
, to which two arguments are passed. The first is the object corresponding to 'this', and the second is something called arguments
.
The variable arguments refers to an array of whatever values were passed to the constructor. This is a special built-in variable provided by JavaScript; arguments
is always available to you inside a function. If you look back into string_test.html, you see that there's a lot of stuff between the parens in
new Test.Unit.Runner({ testStringParseColor: function() { with(this) { assertEqual('#000000', "#000000".parseColor()); assertEqual('#000000', "rgb(0,0,0)".parseColor()); ... }}, ... });
However, all that stuff is bounded by brackets, which suggests that all of that stuff is a hash, a single object. The code could have been written like this.
var myhash = { testStringParseColor: function() { with(this) { assertEqual('#000000', "#000000".parseColor()); assertEqual('#000000', "rgb(0,0,0)".parseColor()); ... }}, ... }; new Test.Unit.Runner(myhash);
So the hash object is the only argument. This means arguments in the line
this.initialize.apply(this, arguments);
is a one-member array. Its length will be important later.
OK, then what is "this"?
Let me take a deep breath. The keyword/variable thingee this
is a slippery fellow. As in any decent OO language, it refers to the current object. Unfortunately, in JavaScript, the current object is often window
, the global object representing the current browser window, and it isn't always easy to tell when this happens. Inside a constructor, this refers to the object that is being created by the constructor. More technically, the new expression creates an object and places a reference to it as
this
at the front of the constructor function's scope chain.
So I'm thinking that "this" inside the constructor must be the new object, our new anonymous instance of Test.Unit.Runner
.
The function apply
is a function that is a member of all function objects. Remember, functions are just data, so if you define a function foo
and then refer to it as foo
in your code, it's essentially just an object with properties and methods. Invoking a function's apply
function invokes the function. Why is apply
necessary? Because the first argument that apply
takes becomes the this
value in the function. Put another way, foo.apply(bar, ...)
is like calling bar.foo(...)
. Why not just call bar.foo
? Because maybe bar
doesn't have a foo
method. Maybe you want to be able to call foo
as though it were a method of some object, but that object is chosen at runtime. If you're used to static typing and class definitions like Java's, this is mind-bending.
But back to the code we're examining. "If you're right," I can hear you saying, "Then initialize
is a function, and initialize.apply(this, arguments)
is the same as calling this.initialize(arguments)
." Yes, that's right. "But," you add with a frown, "The code DOES already contain this.initialize
. Why do we have to call apply
this way?"
This is what I'm not sure of myself. I think it's time to move beyond reasoning and seek some empirical data. Let's fire up Firefox and the exquisite plugin Firebug, and take a look at how this code works.
I'll set a breakpoint in prototype.js at this line:
this.initialize.apply(this, arguments);
Here's a screenshot of what I find.
OK, so all I know about "this" is that it's an object, and it has a bunch of methods. My guess is that it's an instance of Unit.Test.Runner
. How can I find out for sure?
Let's go back to unittest.js and look at the next chunk of code. There's a lot of it, but you'll notice that the curly braces and colons are back, meaning that we're dealing with a hash. I'll elide (lovely word, isn't it?) all the values in the hash, and here is a simplified picture of the block of code:
Test.Unit.Runner.prototype = { initialize: function(testcases) {...}, parseResultsURLQueryParameter: function() {...}, parseTestsQueryParameter: function() {...}, ... getResult: function() {...}, postResults: function() {...}, runTests: function() {...}, summary: {...} }
Now we can make some sense of the code. What is this hash here for? Well, we're assigning it to an lvalue: Test.Unit.Runner.prototype
. Ah ha! prototype -- that's the template for instances of the pseudoclass Test.Unit.Runner
. So the hash is putting all these functions in the prototype so that every instance of Test.Unit.Runner
will have these functions as members. So that means that back in my Firebug window, if "this" is an instance of Test.Unit.Runner
, it should have the seven methods listed in the hash. And sure enough, it does.
But this isn't quite definitive. There could be some other object that has the same methods as Test.Unit.Runner
, and "this" could be referring to that mischievous object. How do we know for sure? Well, I'm going to be just a little bit mischievous myself and put a little bit of graffiti in the unittest.js code:
Test.Unit.Runner.prototype = { my_name: "Test.Unit.Runner", // Kilroy was here. initialize: function(testcases) { ...
If you've never done this in code before, you simply must start. Especially in interpreted code where the result of your meddling is immediate. To wit:
There we go. Also, notice the little clue-prompt style popup in the code window. I've rolled over the appearance of this
in the code, and this helpful little popup appears to let me know that my little marker is present in the code. I can roll over both of the this
s in the line, and I see the same thing. Both times, this
is our new instance. So why, going back to your insightful question earlier, is apply(this, ...)
necessary? I haven't the faintest idea, but I'm sure there was some reason. Let's put the matter aside for now and move on.