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 jsclass
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
Class
have a klass
property that points to the class the object belongs
to:
var Foo = new Class(); var obj = new Foo(); obj.klass === Foo Foo.klass === Class
All classes are instances of the class 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:
obj
is an instance of classFoo
, or of any subclass ofFoo
obj
is an instance of a class that includes the moduleFoo
obj
has been extended using the moduleFoo
Remember that, as in Ruby, modules and classes are objects 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 Module({ speak: function() { return "speak() in ModA"; } }); var ModB = new Module({ speak: function() { return this.callSuper() + ", speak() in ModB"; } }); var ModC = new Module({ include: ModB, speak: function() { return this.callSuper() + ", and in ModC"; } }); var Foo = new 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.
jsclass
searches this list in reverse order when doing method lookups.
Foo.ancestors() // -> [Kernel, ModA, ModB, ModC, Foo]
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') // -> #<Method> 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"]
Method objects
The Module#instanceMethod()
method does not return a bare function; instead
it returns a Method
object. This is a class that jsclass
uses internally
to represent methods stored in modules, and it provides a lot more contextual
information about a method than a bare function would.
A Method
object has the following properties:
module
– theModule
orClass
that the method is defined inname
– the name of the methodarity
– the number of arguments the method explicitly acceptscallable
– the function that provides the method’s implementation
So, for example you can get a method out of a class and find out if it actually came from another method by calling:
klass.instanceMethod('foo').module
Like JavaScript functions, Method
objects respond to call()
and apply()
,
so you can actually pass them to methods that expect callbacks to be passed in.
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 jsclass
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') // -> [#<Method>, #<Method>, #<Method>, #<Method>]