Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prototypal OO with Object.create and named constructors

Tags:

I'm coming to Javascript from a background in Python and Smalltalk, and I appreciate the linage of Self and Lisp in the language. With ECMAScript5, I wanted to try my hand at prototypal OO without the new operator.

Constraints:

  • optional new operator to create classes
  • prototype chain must be correct for instanceof
  • named constructors for WebInspector debugging support
  • alloc().init() creation sequence like Objective-C and Python

Here is my attempt at the implementation to meet the criteria:

function subclass(Class, Base) {
    "use strict";
    function create(self, args) {
        if (!(self instanceof this))
            self = Object.create(this.prototype);
        var init = self.__init__;
        return init ? init.apply(self, args) : self;
    }
    if (Base instanceof Function) Base = Base.prototype;
    else if (Base===undefined) Base = Object.prototype;

    Class.prototype = Object.create(Base);
    Class.prototype.constructor = Class;
    Class.create = create;

    Class.define = function define(name, fn) { return Class.prototype[name] = fn; };
    Class.define('__name__', Class.name);
    return Class;
}

And it seems to work in a simple mockup:

function Family(){return Family.create(this, arguments)}
subclass(Family, Object);
Family.define('__init__', function __init__(n){this.name=n; return this;});

function Tribe(){return Tribe.create(this, arguments)}
subclass(Tribe, Family);
function Genus(){return Genus.create(this, arguments)}
subclass(Genus, Tribe);
function Species(){return Species.create(this, arguments)}
subclass(Species, Genus);

Using the class as a factory function:

var dog = Species('dog');
console.assert(dog instanceof Object);
console.assert(dog instanceof Family);
console.assert(dog instanceof Tribe);
console.assert(dog instanceof Genus);
console.assert(dog instanceof Species);

Or using the new operator:

var cat = new Species('cat');
console.assert(cat instanceof Object);
console.assert(cat instanceof Family);
console.assert(cat instanceof Tribe);
console.assert(cat instanceof Genus);
console.assert(cat instanceof Species);

console.assert(Object.getPrototypeOf(dog) === Object.getPrototypeOf(cat))

Have I overlooked needed features of prototypal OO in my implementation? Are there Javascript conventions or interactions I should make changes for? In summary, what are the "gotcha"s here, and are there any obvious improvements to be made?

I wanted to be DRYer with the constructor definitions, but I found that a function's name property is not writable, and that is what supports the WebKit Inspector's object names. I was able to construct an eval to accomplish what I wanted, but... yuck.