Search code examples
javascriptoopprototypeobject-create

Prototypal OO with Object.create and named constructors


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.


Solution

  • Edit: Oh I see the question now. Answer: no, you got it exactly right. The only way to set the name of a function is when using a function declarations, which means at evaluation time. Thusly you need to have it in the source code (which eval is the back door into). I answered a simpler question previously but with the same gist: Minor drawback with Crockford Prototypical Inheritance. Another resource on this topic is http://kangax.github.com/nfe/

    There is movement to use the displayName property in order to divorce the unchangeable name of a function from it's debugger appearance. This is implemented in Firefox and some other stuff, and is a strawman for inclusion in es6 but as of yet not part of the tentative spec: http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

    Here is a paper from some people working on Chrome about the topic of naming functions http://querypoint-debugging.googlecode.com/files/NamingJSFunctions.pdf

    And here is the chromium issue discussing why it's not implemented yet: http://code.google.com/p/chromium/issues/detail?id=17356

    Onto original answer:

    What you set out to accomplish, you've done it well. A couple examples of similar type stuff I've done:

    First is a simple 'heritable' function which allows you to do stuff like:

    var MyObjCtor = heritable({
      constructor: function MyObj(){ /* ctor stuff */},
      super: SomeCtor,
      prop1: val,
      prop2: val,
      /**etc..*/
    });
    
    
    function heritable(definition){
      var ctor = definition.constructor;
      Object.defineProperty(ctor, 'super', {
        value: definition.super,
        configurable: true,
        writable: true
      });
      ctor.prototype = Object.create(ctor.super.prototype);
      delete definition.super;
    
      Object.keys(definition).forEach(function(prop){
        var desc = Object.getOwnPropertyDescriptor(definition, prop);
        desc.enumerable = false;
        Object.defineProperty(ctor.prototype, prop, desc);
      });
    
      function construct(){
        var obj = new (ctor.bind.apply(ctor, [].concat.apply([null], arguments)));
        ctor.super.call(obj);
        return obj;
      }
    
      construct.prototype = ctor.prototype;
    
      return construct;
    }
    

    The other is in creating structs for use with libffi (node-ffi). Essentially here you have both constructor inheritance and prototype inheritance. You create constructors that inherit from constructors, which create instances of structs. https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

    The use of eval is required in order to create a named function, so that's that if you need a named constructor. I have no qualms about using it where needed.