I was first exposed to metaclass programming through JavaScript, though I didn’t realize it. When I started digging into the guts of Django’s ORM system, I learned how metaclass programming in Python worked and discovered I’d been doing something similar in JavaScript for a while.

So what is it? Some great articles on metaclass programming turn up in Google and are worth a read. But for those of you that would rather just get down to business, I’ll try to summarize the idea really quickly, focusing on what we’re going to be able to do with Javascript.

Metaclass programming is the programming that builds a class. Since most of the time a metaclass simply takes an already declared class and manipulates it, you can think of a metaclass as a template: a class goes in, and a specifically structured class comes out.

Why didn’t I realize that I was using it in JavaScript? Well, mostly because if you want to build a class structure in JavaScript, you are forced to do everything related to OO in JavaScript programatically. Want to do inheritance? There’s no syntax for it, you have to do it programatically. Want to add some functions and properties? You have to do it programatically.

Which is why almost all of the toolkits make some provisions for doing these programatic procedures. All of these tools are related to metaclass programming, but they’re almost all geared to constructing a class. What metaclasses are truly awesome for is manipulating a class, and we’ll be doing that today.

So that’s good to know, but when (and why) would we ever use this? Well, what we’re going to build today is going to be both an excellent example and a possible solution for what a lot of people consider to be a real world problem: declaring private members.

A lot of people have started to prefix their variables with an underscore to indicate that it’s private. What does this look like in practice?

function MyClass(){
  // Some constructor code
}
MyClass.prototype.publicVar = "I'm public";
MyClass.prototype._privateVar = "I'm private";

Even though we create variables using this naming scheme, they never actually become private. This means that they can still be changed by anyone that has access to an instance of our object. Why not use our time today to rectify this?

We’re going to be doing this through a simple function call inside of our constructor. We’ll be naming our metaclass “privatize” and this is how we’ll be using it:

function MyClass(args){
  privatize(this);
}

We’ll create the privatize function in a second. But before we do, you might have noticed that the privatize function will get called every time an instance of this object is created. Although we’ll be making sure that our class only gets manipulated once, we’re going to have to tweak the actual instance as well.

In JavaScript, one main object is shared across all instances of a class. Named constructor, it’s a pointer back to the function object it was created from. So any instances of our MyClass constructor above will have this property pointing back at the MyClass constructor. This means that we’re able to get access to its prototype through the constructor.

Using these two references, we can keep a history of what we’ve done, and we can manipulate the class.

function privatize(instance){
  var c = instance.constructor;
  var p = c.prototype;
  if(!c._privatize){
    c._privatize = {};
  }
}

Now that we have our basic structure, it’s time to start manipulating the prototype. What we’re going to do is take all properties that start with the underscore character (stripping the underscore) and combine them into an array that we can save to the constructor, while deleting them from the prototype. Saving them to the constructor (instead of the prototype) means that these variables will become completely private. If anything subclasses this constructor, their instances won’t be able to see or access these variables.

function privatize(instance){
  var c = instance.constructor;
  var p = c.prototype;
  if(!c._privatize){
    c._privatize = { privates: {} };
    for(var key in p){
      var value = p[key];
      if(key.charAt(0) === "_"){
        c._privatize.privates[key.slice(1)] = value;
        delete p[key];
      }
    }
  }
}

Okay, great, so now people can’t mess with any of the private properties of this object, but we have a new problem: our functions can’t see them either!

We need a solution that’s going to let us do a few things: functions need to be able to see both public and private variables, changes to private variables should stay hidden from the public view of the instance, and changes to public variables should appear in the public view of the instance.

Sandboxing is how we’re going to provide this functionality. Each function is going to run in a special environment that we create for it. All variables will be visible directly on the this variable, but in order to make a change to a public variable, the function will have to use the special field that we’re going to create called public (but you could call whatever you’d like).

function privatize(instance){
  var c = instance.constructor;
  var p = c.prototype;
  if(!c._privatize){
    c._privatize = { privates: {}, functions: {} };
    for(var key in p){
      var value = p[key];
      if(key.charAt(0) === "_"){
        c._privatize.privates[key.slice(1)] = value;
        delete p[key];
      }else if(dojo.isFunction(value)){
        c._privatize.functions[key] = true;
      }
    }
  }

  var context = dojo.delegate(instance, c._privatize.privates);
  context.public = instance;

  for(var fn in c._privatize.functions){
    instance[fn] = dojo.hitch(context, instance[fn]);
  }
}

Let me try to explain what dojo.delegate has created here through an analogy.

Let’s say that you’ve just bought a color-by-numbers book, you bring it home, and you get in trouble with your nephew who also wants to use it. But you don’t feel like driving back to the store and if you color it in, you’ve just ruined it for your nephew. But you have an idea. You take out a sheet of transparency paper, tape it over the page, and start filling it in.

In the same way, think of our instance object as a color-by-numbers page that’s been partially filled in. And think of dojo.declare as putting transparency paper over our instance object. Now when we write variables to this new object, or “color it in”, those changes don’t make it through to the instance object. Writing a variable to the context object that doesn’t exist in the instance is like coloring on the transparency sheet over a blank area. Writing a variable to the context object that exists in the instance is like coloring on the transparency sheet over an area that’s already been filled in. Neither action changes what’s on the page beneath, but coloring over something means we won’t be able to see what was there originally any more.

So now that we have this pretty nice context, we have to make sure that each function actually uses it. We saved a list of functions during the prototype manipulation to give us a quick list of the functions that need to be changed. We can then replace each function with a version that’s been “hitched” to our context.

But if we can’t write anything to the instance object, how will changes to public variables show up on the instance object? Well, the compromise is that special field I mentioned above. It gives us a reference to the original instance object, so that we make public variables public.

Seeing it Work

Use Firefox (with Firebug installed) to view an interactive demo