NetBen Ramblings about JavaScript and frontend development.

24Feb/09Off

So I reinvented prototype.js…

** EDIT, Library name is now JSoo, leaving this post intact to remind me of my stupid name choice for a library **

No, I am not talking about prototype.js from prototypejs.org I've actually created a prototypical Prototype Multiple Inheritance System and Module Pattern decorator library. Considering what the library does, I found Prototype was the best name for it and my appologies in advance to the prototypejs crew for naming it likewise. I also have to add, this is merely a working draft version and imo. good enough to release, namespace issues will be a later concern (though I am using a special namespace pattern for plug and play action ;) )

As a reaction to the Javascript Inheritance Performance post on Ajaxian, I found myself thinking; "Why is everyone trying to superimpose Classical OO on Prototypical OO" and "There should be a better way according to what Douglas Crockford tries to teach us through his articles and book". So I got to work and Prototype + Prototype.Module is my idea on how to handle inheritance in a nice and orderly way.

Other inheritance systems solve it function.prototype = {} >here<.
This inheritance system solves it >here< function.prototype = {}

A complete description of Prototype & Prototype.Module can be found here. *EDIT LINK CHANGED*

The library can be downloaded to play around with here. *EDIT LINK CHANGED*

A quick example to entice and show what the library does.

Prototype example:

The Prototype function basically leaves the functions alone and only handles property hashes, so we can assign them to functions AFTER they're properly defined. It also supports multiple inheritance the prototypical way and allows back references to donor property hashes.

    Prototype("MammaCat", {
        talk: function() {
            return this.name + " says: miauw";
        }
    });

    var MammaCat = function(name) {
        this.name = name;
    }
    MammaCat.prototype = Prototype("MammaCat");

    var Pookie = new MammaCat("Pookie");
    alert(Pookie.talk()) //==> Pookie says: miauw

    Prototype("Kitten", {
        talk: function() {
            return this.name + " says: meep";
        },
        myMommaSays: function() {
            return "my momma " + this["::MammaCat"].talk.apply(Pookie);
        }
    }, "MammaCat"); // <-- Inherit methods from Cat.

    var Kitten = function(name) {
        this.name = name;
    }
    Kitten.prototype = Prototype("Kitten");

    var Bella = new Kitten("Bella");
    alert(Bella.talk()) //==> Bella says: meep
    alert(Bella.myMommaSays()) //==> Pookie says: miauw

Prototype.Module example:

Prototype.Module provides a neat way to organise Module Patterns as well as allow the sharing of private variables over multiple Module Patterns through closure techniques.

    Prototype.Module("combat", function(cache) {
        cache.baseDamage = 10;
        return {
            rake: function() {
                alert(this.name + " rakes for " + cache.baseDamage + " damage");
            }
        }
    });

    Prototype.Module("specialAttacks", function(cache) {
        return {
            claw: function() {
                // yes, we reference a value set in another module pattern through the cache argument!
                alert(this.name + " claws" + (cache.baseDamage + 10) + " damage");
            }
        }
    });

    // the cache argument in the module patterns gets shared as long as you assign the
    // modules in a single call.
    Prototype.Module.attach(Pookie, "combat", "specialAttacks");
    Pookie.rake(); //==> Pookie rakes for 10 damage
    Pookie.claw(); //==> Pookie claws for 20 damage

The code is at version 0.0.1 and is basically a first draft to show how inheritance can be solved the prototypical way. I do understand people want to make JavaScript look and behave more like Java, but it's simply not a classical OO language. Use and embrace the prototypical way of scripting!

I am sure people can find errors, performance issues and have better ideas or suggestions, feel free to post them and I will incorporate it, proper credits will be given ;)

And if someone knows of a better name then Prototype (to avoid possible conflict with the good and nice lads at prototypejs.org), then I am very much open to suggestions.

Filed under: JavaScript, JSoo Comments Off
Comments (11) Trackbacks (3)
  1. I don’t understand this bit:

    myMommaSays: function() {
    return “my momma ” + this["::MammaCat"].talk.apply(****Pookie***);
    }

    Why is a prototype for a kitten using a reference to a concrete MammaCat. Erm, if you see what I mean?

  2. I don’t care what it does or how good is does it, you obviously need to change the name if you want to take it anywhere.

  3. Maybe you should name it p-type.

  4. @Ben – On your description/documentation page you state “Any changes made to the stored property hash in the Prototype decorator, will also apply to the function’s prototype, but NOT on objects instantiated by the function using the “new” keyword…”.

    I think FireBug might be misleading here.

    var myClass = function (){};
    myClass.prototype = Prototype(“FirstPrototype”);

    var foo = new myClass();

    Prototype(“FirstPrototype”, {
    something: function() { alert(‘something’) }
    });

    },

  5. foo.something() -> alerts “something”;

  6. Cool stuff!

    As for the name, how about “Prototipo” (Italian for prototype ;) )

  7. Please rename.

  8. Ok ok, I’ve renamed the library and function to JSoo (jsoo.js) and am currently busy replacing Prototype for JSoo in the page article.

    @John-David Dalton, Ah yes, I hadn’t thought about the situation you sketched. Just fixed it and it will work now. Thanks for pointing it out ;)

    @Jerome, in the example, I stored the property hash under the name “MammaCat” and then I created a function called MammaCat to tie the hash to the prototype attribute. The function could be called PoppaCat, the prototype reference will still be stored under MammaCat along with other references to the property hash. You see, I make the property hash leading, not the function. I want to know where the methods are comming from through decoupling it from functions I happened to attach it to.

    Since the new “Kitten” property hash inherits methods from the “MammaCat” property hash, you’d have to reference the “MammaCat” property hash for the original method, which you now can. This allows for grabbing methods from multiple donors (multiple inheritance) and still be able to reference to original methods in case you override one on the current object.

  9. Hey Ben, thanks for this contribution to the inheritance problem/solution space. I think the notion of a prototype/module registry is an interesting one, but I’m not sure it solves the problems the previously mentioned inheritance solutions are looking to address. Specifically, much of the value in the other inheritance libraries is in how they automatically scope parent methods to the object instance in question, elliminating the need for the apply() call.

    The fact that JSoo requires something like, ‘this["::MammaCat"].talk.apply(Pookie)’ is likely to be a showstopper for many people.

    Granted, this is all syntactic sugar, but fundamentally I believe that’s what people are after. It’s much easier to write $super(…) or this.parent(…). A prototype registry will of course be more performant – you’re closer to the “bare metal” of the implementation after all – but it’s about as easy to write MommaCat.prototype.talk.apply() as it is to write this['::super'].talk.apply(). And at least with that approach, you’re not cluttering up prototype objects with ‘::super’ and ‘::MommaCat’ keys.

  10. @robert, thanks for dropping a line, your JSLitmus post kinda started me on this, here’s long reply, hope you read it ;)

    First of all, yes the rescoping of the original method is a great convenience, however to accomplish that, you need to curry/bind/wrap/nest functions and still use apply/call a lot, which creates quite a performance drain the further you subclass as your JSLitmus tool shows.

    Furthermore, .base(), ._super() and $super, only allow access to 1 single function in the superclass. This is good enough if all you want is to call the original method from the override method. You actually have to do a lot of overriding if you want to call several different super methods from one override method OR fall back to Super.prototype.method.call(this) making .base(), ._super() and $super redundant and sluggish to begin with. Again, nothing wrong with it if thats all you need and convenience is more important then performance.

    As for referencing the prototype of a superclass directly, it’s just as easy to write as the JSoo way yes, but this causes a performance drop (% differs per JS engine) as the runtime has to search the scope chain for the reference.

    JSoo’s inheritance system is actually very similar to YUI’s way of inheritance and is indeed the best for performance, allows the most control and offers the best diversity. JSoo just offers a better convenience way of referencing super/donor property hashes.

    example: C inherits from B inherits from A.
    YUI:
    this.constructor.superclass.constructor.superclass.method.call(this)
    JSoo:
    this["::A"].method.call(this)

    I prefer JSoo’s way ;) To each his own though.

    As for cluttering the property hash (prototype), thats why I use the :: prefix, you can easily distinguish the super/donor references from other properties. It’s a usefull reference property and neatly tucked away in it’s own corner of the proverbial room. I think it rests on what your definition of clutter is and I ask you to really think hard about that one ;)

    As for people who’d use it, yes, there’s an audience for convenience and an audience for performance. JSoo tries to provide convenience with the least sacrifice of performance, whilst maintaining control and options.

  11. Oh and I have to confess, I have broken some eggs in JSoo since it doesn’t support linear inheritance properly like YUI does, though the problem only shows when you change the property hashes AFTER you initiated instances and only in multi level inheritance, quite obscure actually… still a challenge to fix ^^