Skip navigation.

Classes in JavaScript

JavaScript is an prototype-based object-oriented language. This is much less common than class-based object-oriented languages. This makes JavaScript a little bit hard to learn for most experienced programmers. The good thing is that prototypes are more flexible than classes, and you can actually implement class-based object-orientation in a prototype-based language.

There are a number of JavaScript frameworks that among other things also implements class-based object-orientation. Two of these I have been looking closely at, Prototype and MooTools.

I like both of them, but in my opinion, MooTools class implementation looks a little better. However, MooTools class implementation has a lot of overhead per method call. Prototype is faster. But both are too complex in implementation for my taste.

So, with the experience of these two class implementations, I decided to try to come up with my own class implementation. This is what I wanted to accomplish:

  • A compact and easy to understand implementation
  • An implementation that has very little run-time overhead
  • Easy to use
  • Support extending a class after its creation, so you can add functions to already created objects of that class

As always, to accomplish what you want to, you have to compromise. The compromise I decided to do was to make it a little bit harder to call the parent's implementation of the same function, and also that the initialization code of a parent needs to be explicitly called.

There are still a few optimizations left to do, it will not add that many lines, but I decided to keep it a little bit simpler, 50 lines of code is enough for a blog post.

$class: the most important function. It creates and returns a new class. If the first argument is a function, that is treated like this will be the parent class. All remaining arguments are treated like they are objects, and all members (functions as well as other data) are copied to the class. If initialize is one of the members, it will be called when you call new on the new class. All arguments sent to new are sent to the initialize function.

$new is a special member in the class that will be used in the combination with new when the class is used as a parent class. $parent is there to be able to implement the $super and $superinit functions.

$super: call the parent's implementation of a function. self is the this object, and fname is the name of the method that should be called. The rest of the arguments are sent to the parent function.

$superinit: call the parent's initialize implementation. self is the this object. The rest of the arguments are sent to the parent function.

$extend: extend the class klass with new members. These members are available in all current and future objects created from this class. All the arguments except for the first are treated like objects, and all members in those objects are inserted in the class.

function $super(self, fname) {
  var args = [];
  for (var i = 2; i < arguments.length; i++)
    args[i - 2] = arguments[i];
  return $super.caller.$base.$parent.prototype[fname].apply(self, args);
}

function $superinit(self) {
  var args = [];
  for (var i = 1; i < arguments.length; i++)
    args[i - 1] = arguments[i];
  return $superinit.caller.$base.$parent.prototype["initialize"].apply(self, args);
}

function $extend(klass) {
  var dest = klass.prototype;
  for (var i = 1; i < arguments.length; i++) {
    var src = arguments[i];
    for (var x in src) {
      dest[x] = src[x];
      if (typeof dest[x] == "function")
        dest[x].$base = klass.prototype;
    }
  }
}

function $class() {
  var klass = function() { if (this.initialize) return this.initialize.apply(this, arguments); };
  var parent, start, proto_obj;
  if (typeof arguments[0] == "function")
    parent = arguments[0];
  if (parent) {
    proto_obj = new parent.$new;
    start = 1;
  } else {
    proto_obj = {};
    start = 0;
  }
  for (var i = start; i < arguments.length; i++) {
    var src = arguments[i];
    for (var x in src) {
      proto_obj[x] = src[x];
      if (typeof proto_obj[x] == "function")
        proto_obj[x].$base = proto_obj;
    }
  }
  proto_obj.$parent = parent;
  klass.prototype = proto_obj;
  klass.$new = function() {};
  klass.$new.prototype = proto_obj;
  return klass;
}


The code is not well tested yet, so there may be bugs. I will update this blog post when I find something that needs to be changed. I will also add an extra blog post with examples that uses the class library.

Update 1: Had to add $base to each function in a class, to be able to solve the problem with infinite recursion when trying to call the super function.
Update 2: In $superinit, the parameter needs to be called self, not obj.

Unite and UserJSNiclass tutorial (my JavaScript class implementation)

Comments

Nicklas Larsson 4. October 2009, 07:13

And I have already found the first bug. If you call $super in a class that is inherited by another class, and you instantiate an object of the inherited class and call the function, you get infinite recursion. I will try to fix it soon, worst case is that the design is completely flawed.

Nicklas Larsson 4. October 2009, 08:49

The infinite recursion bug has been fixed in Update 1.

It is still not very well tested, but it seems to work.

Because of $base, it is harder to share functions between classes. (that is, if you add the same function to two different classes, $base will only be right for the one that was added last, which means that $super will not work, so as soon as you want to use $super, you will not be able to share that function between classes)

Nicklas Larsson 4. October 2009, 11:07

Fixed a bug in $superinit in Update 2.

Write a comment

You must be logged in to write a comment. If you're not a registered member, please sign up.

December 2009
M T W T F S S
November 2009January 2010
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31