inheritance and prototype.js
I'm reviewing the content of this article to reflect the new implementation I made (slight differences, more dynamic) for Prototype Window Class we are curently rewriting with Sébastien Gruhier. Stay tuned !
Good news !
It seems that inheritance finally went in Prototype trunk.As I said in my last article, I was myself working on some implementation of inheritance that could be integrated to Prototype so I was really happy to find a lot of people discussing about inheritance on the Prototype core mailing list.
I love the AspectOrientedProgramming-like way of calling the parent method that is, if a method want to call its parent one (the one defined in the superclass' prototype), it simply has to receive it as first argument called $super.
So I decided to mix all good ideas of each implementation. Here is the listing of some propositions and the source code (you can download a patch for the revision 7299). Let's give your opinion/critics !
Propositions :
Base class
There is one base class called Base, every class will be a subclass of Base.
It defines a method extend in its prototype.
This method simply extend the current object with a given object, allowing to define methods which can call their parent if they include $super in their signature.
Okay so basically, object.extend(properties) is the
Object.extend(object, properties) on steroïds.
Classes
Classes have a property constructor set to Class
Classes have methods coming from Class.prototype but are not dynamically linked to this prototype. These are just the usefull methods we want all classes to understand.
Classes methods are not shared with subclasses.
Class methods are :
Klass.extend(object): which extends the current class with the given object (likeObject.extend(Klass, object))Klass.include(object): which extends Klass prototype with the given object.Klass.classEval(declaration): see classEval
Class creation
Class.create or new Class can be called 3 different ways :
- with no argument to create a class like within Prototype.
- with one argument which can be either a class to inherit from (if it's a function) or a declaration body (if it's an object).
- with two arguments : superclass, declaration body
Base is taken.
The declaration body will be an argument to the classEval class method
classEval
Klass.classEval(declaration) : declaration is an hash with special properties :
selfis an hash containing class methods and attributesincludeis a module or an array of modules to include withKlass.include
classEval is like reopening the class, it could be named open.
Source code
The source code has been changed since, you can view the last version.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 82 83 |
var Class = Object.extend(function(superclass, body) { if (typeof superclass != 'function') body = superclass, superclass = Base; var cls = function() { if (arguments[0] !== Class._extending_ && this.initialize) this.initialize.apply(this, arguments); } // Getting properties from Class.prototype Object.extend(cls, this); Object.extend(cls, { superclass: superclass, prototype: new superclass(Class._extending_), subclasses: [] }); cls.constructor = Class; cls.prototype.constructor = cls; if (body) cls.classEval(body); superclass.subclasses.push(cls); if (superclass.inherited) superclass.inherited(cls); if (cls.afterCreation) cls.afterCreation(); return cls; }, { _extending_: new Object(), // backward compatibility create: function(superclass, body) { return new this(superclass, body); } }); Class.prototype = { extend: function(object) { Object.extend(this, object); }, include: function() { this.prototype.extend(mixin); if (Object.isFunction(mixin.included)) mixin.included(this); }, classEval: function(body) { this.extend(Object.erase(body, 'self') || {}); if (typeof body.include == 'object') [ Object.erase(body, 'include') ].flatten().each(this.include.bind(this)); this.prototype.extend(body); } }; var Base = Object.extend(function() {}, { subclasses: [], constructor: Class }); Base.prototype = { extend: function(object) { var ancestor = this.constructor.superclass.prototype; for (var property in object) { if (Object.isFunction(object[property]) && Object.isFunction(ancestor[property]) && object[property].toString().include('$super')) { var method = object[property]; this[property] = Object.extend(ancestor[property].wrap(method), { valueOf: function() { return method; }, toString: function() { return method.toString(); } }); } else this[property] = object[property]; } } } |
Comments
-
You are damn smart... Great work... I think I'm gonna rewrite the Window lib myself... I didn't like past version 1.0, and I'm the one who came up with the constraint stuff for it, even though you won't find me in the credz ;(
-
I take it back... The window lib rocks the way I wanted it to to begin with... Funny since prototype adds 40k of code to the version I was using with 1.0 and it runs faster... very funny. I also like the concept from jquery where the window object methods return references to themselves so you can call methods and basically instruct... "and then...." Let me know if I can help in any way, I donated to this project once, After seeing your Class inheritence donation, I definitely think you deserve one too now!