inheritance and prototype.js

written by sam on August 9th, 2007 @ 01:17 PM

I'm reviewing the content of this article to reflect the new implementation I made (slight differences, more dynamic) for Prototype Window Class we are curently rewriting with Sébastien Gruhier. Stay tuned !

Good news !

It seems that inheritance finally went in Prototype trunk.

As I said in my last article, I was myself working on some implementation of inheritance that could be integrated to Prototype so I was really happy to find a lot of people discussing about inheritance on the Prototype core mailing list.

I love the AspectOrientedProgramming-like way of calling the parent method that is, if a method want to call its parent one (the one defined in the superclass' prototype), it simply has to receive it as first argument called $super. So I decided to mix all good ideas of each implementation. Here is the listing of some propositions and the source code (you can download a patch for the revision 7299). Let's give your opinion/critics !

Propositions :

Base class

There is one base class called Base, every class will be a subclass of Base.
It defines a method extend in its prototype. This method simply extend the current object with a given object, allowing to define methods which can call their parent if they include $super in their signature.

Okay so basically, object.extend(properties) is the Object.extend(object, properties) on steroïds.

Classes

Classes have a property constructor set to Class

Classes have methods coming from Class.prototype but are not dynamically linked to this prototype. These are just the usefull methods we want all classes to understand.
Classes methods are not shared with subclasses.

Class methods are :

  • Klass.extend(object) : which extends the current class with the given object (like Object.extend(Klass, object))
  • Klass.include(object) : which extends Klass prototype with the given object.
  • Klass.classEval(declaration) : see classEval

Class creation

Class.create or new Class can be called 3 different ways :
  • with no argument to create a class like within Prototype.
  • with one argument which can be either a class to inherit from (if it's a function) or a declaration body (if it's an object).
  • with two arguments : superclass, declaration body
If no superclass is given, Base is taken. The declaration body will be an argument to the classEval class method

classEval

Klass.classEval(declaration) : declaration is an hash with special properties :
  • self is an hash containing class methods and attributes
  • include is a module or an array of modules to include with Klass.include
Using classEval is like reopening the class, it could be named open.

Source code

The source code has been changed since, you can view the last version.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

  var Class = Object.extend(function(superclass, body) {
    if (typeof superclass != 'function')
      body = superclass, superclass = Base;

    var cls = function() {
      if (arguments[0] !== Class._extending_ && this.initialize)
        this.initialize.apply(this, arguments);
    }
    // Getting properties from Class.prototype
    Object.extend(cls, this);
    Object.extend(cls, {
      superclass: superclass,
      prototype: new superclass(Class._extending_),
      subclasses: []
    });

    cls.constructor = Class;
    cls.prototype.constructor = cls;

    if (body) cls.classEval(body);

    superclass.subclasses.push(cls);
    if (superclass.inherited) superclass.inherited(cls);
    if (cls.afterCreation) cls.afterCreation();

    return cls;
  },
  {
    _extending_: new Object(),

    // backward compatibility
    create: function(superclass, body) {
      return new this(superclass, body);
    }
  });

  Class.prototype = {  
    extend: function(object) {
      Object.extend(this, object);
    },  

    include: function() {
      this.prototype.extend(mixin);
      if (Object.isFunction(mixin.included))
        mixin.included(this);
    },

    classEval: function(body) {
      this.extend(Object.erase(body, 'self') || {});
      if (typeof body.include == 'object')
        [ Object.erase(body, 'include') ].flatten().each(this.include.bind(this));
      this.prototype.extend(body);
    }
  };

  var Base = Object.extend(function() {}, {
    subclasses: [],
    constructor: Class
  });

  Base.prototype = {
    extend: function(object) {
      var ancestor = this.constructor.superclass.prototype;

      for (var property in object) {
        if (Object.isFunction(object[property]) && 
            Object.isFunction(ancestor[property]) &&
            object[property].toString().include('$super')) {

          var method = object[property];

          this[property] = Object.extend(ancestor[property].wrap(method), {
            valueOf: function() { return method; },
            toString: function() { return method.toString(); }
          });

        }
        else
          this[property] = object[property];
      }
    }
  }

Comments

  • Pedram N on 10 Nov 06:21

    You are damn smart... Great work... I think I'm gonna rewrite the Window lib myself... I didn't like past version 1.0, and I'm the one who came up with the constraint stuff for it, even though you won't find me in the credz ;(
  • Pedram N on 22 Nov 00:39

    I take it back... The window lib rocks the way I wanted it to to begin with... Funny since prototype adds 40k of code to the version I was using with 1.0 and it runs faster... very funny. I also like the concept from jquery where the window object methods return references to themselves so you can call methods and basically instruct... "and then...." Let me know if I can help in any way, I donated to this project once, After seeing your Class inheritence donation, I definitely think you deserve one too now!

Post a comment

Options:

Size

Colors