public
Last active — forked from tobytailor/def.js

  • Download Gist
def.js
JavaScript
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
/*
* def.js: Simple Ruby-style inheritance for JavaScript
* http://github.com/tobeytailor/def.js
*
* Copyright (c) 2010 Tobias Schneider
* This script is freely distributable under the terms of the MIT license.
*/
(function(global) {
 
// used to defer setup of superclass and plugins
var deferred;
 
function addPlugins(plugins) {
augment(this.prototype, plugins);
return this;
}
 
// TODO: Ensure we fix IE to iterate over shadowed properties
// of those further up the prototype chain. There is also a
// bug in Safari 2 that will match the shadowed property and
// the one further up the prototype chain.
function augment(destination, source) {
for (var key in source) {
destination[key] = source[key];
}
}
 
// dummy subclass
function Subclass() { }
 
function def(klassName, context) {
context || (context = global);
 
// create class on given context (defaults to global object)
var Klass =
context[klassName] = function Klass() {
// called as a constructor
if (context != this) {
// allow the init method to return a different class/object
return this.init && this.init.apply(this, arguments);
}
// called as a method
// defer setup of superclass and plugins
deferred._super = Klass;
deferred._plugins = arguments[0] || { };
};
 
// add static helper method
Klass.addPlugins = addPlugins;
 
// called as function when not
// inheriting from a superclass
deferred = function(plugins) {
return Klass.addPlugins(plugins);
};
 
// valueOf is called to setup
// inheritance from a superclass
deferred.valueOf = function() {
var Superclass = deferred._super;
if (!Superclass) return Klass.valueOf();
 
Subclass.prototype = Superclass.prototype;
Klass.prototype = new Subclass;
 
Klass.superclass = Superclass;
Klass.prototype.constructor = Klass;
Klass.addPlugins(deferred._plugins);
 
// the result of the inheritance experession
// isn't normally captured so this is really edge
// case stuff here and of little practical use
return Klass.valueOf();
};
 
return deferred;
}
 
// expose
global.def = def;
})(this);
gistfile2.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Example
// The bit shift `<<` is just a snazzy way to invoke the valueOf() of the subclass.
// You could use `+` or `<` or `%` or `^` or `*` or well you get the idea.
 
def ('Person') ({
'init': function(name) {
this.name = name;
},
'speak': function(text) {
alert(text || 'Hi, my name is ' + this.name);
}
});
 
def ('Ninja') << Person ({
'kick': function() {
this.speak('I kick u!');
}
});
 
var ninjy = new Ninja('JDD');
ninjy.speak();
ninjy.kick();

Brilliant!
May be add "super" attribute in constructor function?

@donnerjack13589 Sure thing, a lot can be done still.

Also, may be add plugins not only to prototype but to constructor too?

True. Ya, I don't know how far I want to take this.

I do a lot of this in FuseJS already.

I forked Tobias' gist primarily to clean it up and add comments explaining the coolness ;D

Clean up? Are you joking?
You've rewrited most of it. Only idea has survived

That's a very interesting approach @jdalton...

upz, congratulations to both @tobeytailor and @jdalton

This is really neat.

Two questions though:

How does the << syntax work?

Isn't superclass supposed to be a property of the instances you create? It's not coming through. If it's not meant to be there, how does method overloading work?

@kirbysayshi

  1. I added a comment to the usage example explaining your first question.

  2. The superclass property is not on the subclass instance but on the subclass constructor.

    Method overloading and a magic super method isn't supported in this gist.

Regardless of the magic aspect, there is no way to do:
this.constructor.superclass.speak("super text");
from within Ninja's speak method, correct?

I just want to make sure I'm not doing something wrong :)

@kirbysayshi
Look at this:
http://gist.github.com/475129

Sorry for comments on russian. But in this fork you can use your code.

@jdalton
May be add plugins not only to prototype?

@donnerjack13589

git remote add donnerjack13589 git://gist.github.com/475129.git
git pull donnerjack13589 master --commentignore=russian

If only :). Thanks for the info!

Tobias has set up a github repository for this now :D
http://github.com/tobeytailor/def.js

Yep, will look tomorrow. But I don't like his super feature (it makes code bigger)

@jdalton http://github.com/donnerjack13589/def.js
I've added my fixes here, also I've added multiple inheritance.
def ('a') << b()
def ('a') << c()

Also attributes/methods of superclass(and other parent classes) can be accessed from child.
def ('b') ({a:1})
def ('a') << b()
a.a === 1

@donnerjack

I don't see any real inheritance being set up.
You are just porting methods from one object two another in a for...in loop

@jdalton

Real inheritance can be set up only for __super_ class, cause js doesn't support real multiple inhertance.
But this methods will be in every object, created from this class.

And also you can do following:
def ('a') ({a : 1})
def ('a') ({b : 1})

Ok. So it's more like single inheritance + mixins

There is some prob in IE with

if (this != context)

as I mentioned on Twitter, it doesn't work in IE, so I replaced it with this.constructor == Klass as a quick fix. However, I found out that

if (context != this)

already works fine ;) Yet another crazy IE thing. Try to attach via debugger to IE (via standard Developer Tools of IE8, put breakpoint there, and you'll see this value confusion). So, I've changed this.constructor == Klass to if (context != this).

Also, I first used one < (as Ruby has), but not <<, but then found out that Chrome can't handle it, so I backed it to <<.

http://github.com/DmitrySoshnikov/def.js

Dmitry.

Right, the global object is notorious for it's weird matching in JScript:
alert(window == document); // true;
alert(document == window); // false

This is why I avoid that gotcha here;

I should have guessed it would carry over to this

alert((function(x){ return x})(this) == window); // true
alert((function(x){ this.foo=function() { return this == x }; return this.foo() })(this)); // false
alert((function(x){ this.foo=function() { return x == this }; return this.foo() })(this)); // true

Yeah, I knew about window == document (and vice-versa) issue in IE, but the second case was not so easy to catch at first glance.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.