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:
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.
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.