Reflection
Reflection is the process of inspecting the structure of a program at runtime, and
potentially modifying that structure dynamically. Ruby has some very useful
reflection features and JS.Class incorporates a few of them.
Object properties
You sometimes want to find out which class an object belongs to, either to do type
checks or to call methods from that class. All objects created from JS.Class
have a klass property that points to the class the object belongs to:
var Foo = new JS.Class();
var obj = new Foo();
obj.klass === Foo
Foo.klass === JS.Class
All classes are instances of the class JS.Class, just like in Ruby. In addition,
all objects have an isA() method. obj.isA(Foo) returns true if any of the
following are true:
objis an instance of classFoo, or of any subclass ofFooobjis an instance of a class that includes the moduleFooobjhas been extended using the moduleFoo
Remember that, as in Ruby, modules and classes are object too, so they have all the standard methods objects have.
Module and class reflection
Both modules and classes have set of methods that allow you to inspect the inheritance tree, to inspect the method lookup process and to extract individual methods. Let’s set up a few modules to work with:
var ModA = new JS.Module({
speak: function() {
return "speak() in ModA";
}
});
var ModB = new JS.Module({
speak: function() {
return this.callSuper() + ", speak() in ModB";
}
});
var ModC = new JS.Module({
include: ModB,
speak: function() {
return this.callSuper() + ", and in ModC";
}
});
var Foo = new JS.Class({
include: [ModA, ModC],
speak: function() {
return this.callSuper() + ", and in class Foo";
}
});
The ancestors() method returns a list of all the classes and modules that a module
inherits from, with more ‘distant’ ancestors at the start of the list. JS.Class searches
this list in reverse order when doing method lookups.
Foo.ancestors()
// -> [JS.Kernel, ModA, ModB, ModC, Foo]
If you’re debugging using Firebug, it will just print Object or function() for
each of those:
Foo.ancestors()
// -> [Object, Object, Object, Object, function()]
You might not find that very helpful, but if you use StackTrace
you can find the names of the modules in the tree:
JS.StackTrace.nameOf(Foo.ancestors())
// -> ["JS.Kernel", "ModA", "ModB", "ModC", "Foo"]
If that’s not enough information, you can ask JS.Class to explicitly look up methods
for you so you can see the order that inherited methods will be called in. You can
call toString() on any JavaScript function to read its source code. lookup()
returns functions in reverse order; the last function in the list will be the first
to be called, and if it uses callSuper() the penultimate function will be called,
and so on.
Foo.lookup('speak')
// -> [function(), function(), function(), function()]
Finally, you can extract a single named method from a module using instanceMethod(), and
get a list of all the instance methods in a class using instanceMethods. Calling
instanceMethods(false) returns the methods from only that class/module, ignoring
iherited methods. To get all the methods defined on a single object, use methods().
ModC.instanceMethod('speak')
// -> function()
Foo.instanceMethods()
// -> ["speak", "__eigen__", "equals", "extend", "hash",
// "isA", "method", "methods", "tap", "wait", "_",
// "enumFor", "toEnum"]
Foo.instanceMethods(false)
// -> ["speak"]
var f = new Foo();
f.methods()
// -> ["speak", "__eigen__", "equals", "extend", "hash",
// "isA", "method", "methods", "tap", "wait", "_",
// "enumFor", "toEnum"]
The eigenclass
All objects, modules and classes have what’s called an eigenclass to store their
singleton methods. In Ruby, the eigenclass is a real class
but in JS.Class it’s implemented as a module. (This distinction doesn’t really
matter as you’re unlikely to want to instantiate or subclass it.) You can access
the eigenclass of any object by calling its __eigen__() method. For example,
you could inspect the call order of an inherited method using the eigenclass:
var obj = new Foo();
obj.__eigen__().lookup('speak')
// -> [function(), function(), function(), function()]