Updated the 08/02/2013
I really enjoy to organize my Javascript code as objects and get profit by using inheritance.
“Approaching Javascript as Javascript and not as Java”, i read that so many times; Javascript is / can be OOP and i always thought about OOP as something good for code reusing.
I think the OOP nature of Javascript is all around the prototypal chain; a constructor function is the only way to define objects that achives these two points:
- members on the prototypal chain
- the constructor property and the instance of operator working as intended
What i am going to introduce here is a function Class that uses another syntax to achieve the same, native result than the constructor functions; i am avoiding closures as class pattern because they don’t store members on the prototype (but they are a good way to define private members).
I think that the Class function makes the syntax less redundant and verbose, moving the focus from the function to the prototype. Also, it provides tools for code reusing: Javascript has native inheritance but not native tools to use it.
A more expressive syntax to write objects in OOP approach, as Class function tries to provide, is in my opinion a good way to help the development.
The Class function:
Goals
- Encourage objective Javascript instead of sequential programming (!)
- MooTools syntax compatibility (means be allowed to move the code on MooTools or project like moo4q in the future)
- Get an unique class-pattern (avoiding mixed code like using prototype and closures patterns togheter)
- Moving the focus from the function to the prototype, for the functions constructor modelling
- Get lighter classes by provides tools for code reusing
- Class powered by really light code, easy to be mantained by me (or you!)
- Basic functionalities from MooTools: extend, implement, parent method, the function “initialize” as constructor (and as minor: setOptions)
About performance
- At class generation time: the class generation’s code runs only once, to create the class; no performance decreasing
- Using Extends, functions on the sub are wrappers of the super’s functions with the same name. The functions stack grows with it!
Quick demo here
Github page to download sources here
How to use it:
Download the file class-min.js from scr-min/ here and include it in your pages; call it in this way:
var MyClass = new Class({ /* properties */ });
The keywords for Class are: Extends, Implements ; the function initialize is called as constructor of the class. Inside methods, for classes using Extends, you can call this.parent(/*args*/) to call the corrispondent super class method.
Check the following examples to know more.
Class Example
var Cat = new Class({ name: "", initialize: function(name) { this.name = name; } }); var myCat = new Cat('Micia'); alert(myCat.name); // alerts 'Micia'
Extends Example
var Animal = new Class({ age: -1, initialize: function(age) { this.age = age; } }); var Cat = new Class({ Extends: Animal, name: "", initialize: function(name, age) { this.parent(age); // calls initalize method of Animal this.name = name; } }); var myCat = new Cat('Micia', 20); alert(myCat.name); // alerts 'Micia' alert(myCat.age); // alerts 20 alert(myCat.constructor == Animal); // alerts true alert(myCat instanceof Cat); // alerts true alert(myCat instanceof Animal); // alerts true
Implements Example
var Animal = new Class({ age: -1, initialize: function(age) { this.age = age; }, setAge: function(age) { alert("setAge from Animal"); this.age = age; } }); var Cat = new Class({ Implements: Animal, // overriding: no parent method for Implement! setAge: function(age) { alert("setAge from Cat"); this.age = age } }); var myCat = new Cat(); myCat.setAge(30); // alerts "setAge from Cat" alert(myCat.constructor == Cat); // alerts true alert(myCat instanceof Cat); // alerts true alert(myCat instanceof Animal); // alerts false
I see it better in this example:
var DIE = function() { throw new Error("Method not implemented"); }; var Animal = new Class({ age: -1, setAge: function(age) { DIE(); }, die: function() { DIE(); } }); // Cat has an implementation for the method "setAge" // but not for the method "die" var Cat = new Class({ Implements: Animal, setAge: function(age) { this.age = age } }); var myCat = new Cat(); myCat.setAge(30); alert(myCat.age); // alerts 30 // throws the error "Method not implemented" // and stops the execution myCat.die();
Objects with the same name on sub and super are merged
var Animal = new Class({ options: { sex: null, kind: null }, initialize: function(age){ this.age = age; } }); var Cat = new Class({ Extends: Animal, options: { kind: "cat", favouriteFood: "fish" }, initialize: function(name, age){ // it calls initalizes method of Animal class this.parent(age); this.name = name; } }); // check your console: // {sex: null, kind: cat, favouriteFood: fish} Cat.prototype.options;
Properties, as objects or array, are cloned for every instance
When you define a class using Class, the properties as objects or arrays (special case for functions follows) are cloned for every instance, they are not taken from the prototype chain:
var Animal = new Class({ attributes: ["sex", "kind"] }); var myPuppy = new Animal(), myOtherPuppy = new Animal(); // in pure Javascript, the following alerts would print true; // they print false using Class alert(myPuppy.array === Animal.prototype.array) //alerts false alert(myOtherPuppy === Animal.prototype.array) // alerts false alert(myPuppy.array === myOtherPuppy.array) // alerts false // one more example: // the prototypal chain is not involved with // arrays (here) and objects myPuppy.attributes[0] = "new value"; alert(myPuppy.attributes); // alerts "new value","kind" alert(myOtherPuppy.attributes); // alerts "sex","kind" alert(Animal.prototype.attributes); // alerts "sex","kind"
As you can see, three different arrays are instantiated. About functions, thay are taken from the prototype chain but that is not true when a class extends another class and they have a method defined using the same name:
var Animal = new Class({ sayHi: function(){ // not the best example for Animal... alert("Hi!"); } }); var Cat = new Class({ Extends: Animal, sayHi: function(){} }); // "Extends" not involved var myCat = new Animal(), myOtherCat = new Animal(); alert(myCat.sayHi === myOtherCat.sayHi && myOtherCat.sayHi === Animal.prototype.sayHi) // alerts true myCat.sayHi.ciao = "ciao"; // alerts "ciao ciao" alert(myOtherCat.sayHi.ciao + " " + Animal.prototype.sayHi.ciao); // "Extends" involved var myCat = new Cat(), myOtherCat = new Cat(); alert(myCat.sayHi === myOtherCat.sayHi); // alerts false alert(myOtherCat.sayHi === Animal.prototype.sayHi); // false alert(myCat.sayHi === Animal.prototype.sayHi); // alerts false myCat.sayHi.ciao = "ciao"; // alerts "undefined undefined" alert(myOtherCat.sayHi.ciao + " " + Animal.prototype.sayHi.ciao);
What happens unsing “Extends” is that functions are wrappers to the super’s methods (that is needed to use the “parent” method).
Private stuff Example
var Cat; (function() { var myPrivateProperty = "ciao"; Cat = new Class({ initialize: function(name){ this.name = name; }, getMyPrivateProperty: function() { return myPrivateProperty; } }); })(); var myCat = new Cat("puppy"); alert( myCat.getMyPrivateProperty() ); // alerts "ciao!" // error: myPrivateProperty is not defined alert( myPrivateProperty );
Ok… i really don’t like that too but it works, it is a start!
To do
- ย The setOptions method is missed at the moment, i think it is really useful, inside the constructor, in a lots of situations.
- As minor, classes from MooTools get these properties on their prototype:
parent, $constructor
The same properties are missed, or there are but with a different behaviour, using my Class function - The same as above is for the instances of a class defined by MooTools: they get these property (missed or not the same behaviour using mine):
$caller, caller, $constructor, parent
Conclusions
I think this code could be interesting for who is using jQuery or any other toolkit / framework without any Class definition tools… or it can be just an interesting case study!
Foot notes
Implement and Extend functions are designed to extend the native object Function’s prototype; if you don’t like to use the Class function you can do that:
Function.prototype.extend = window.InheritanceApi.Extend; Function.prototype.implement = window.InheritanceApi.Implement; var Super = function(){}; Super.prototype.sayHi = function(){ alert("Hi!"); }; var Sub = function(){}, Sub2 = function(){}; Sub.extend( Super ); Sub2.implement( Super ); var test = new Sub(), test2 = new Sub2(); test.sayHi(); // alerts Hi! test2.sayHi(); // alerts Hi!
Some links
- http://mootools.net/docs/core/Class/Class
- http://ejohn.org/blog/simple-javascript-inheritance/
- http://www.crockford.com/javascript/inheritance.html
- http://javascript.crockford.com/prototypal.html
- http://en.wikipedia.org/wiki/Prototype-based_programming
- http://moo4q.com/
- https://groups.google.com/forum/?fromgroups=#!topic/jsmentors/135BxpwoPOs
- https://groups.google.com/forum/?fromgroups=#!topic/comp.lang.javascript/1M6wjJhY9sk
Thanks for reading!
Very usefull Inheritance tool.Thanks!
Just try to run the code below:
var testClass = new Class({
testTwo: function () {
},
test: function (i) {
console.log(“parent ” + i)
}
});
var childTest = new Class({
Extends: testClass,
testTwo: function () {
},
test: function (i) {
console.log(i);
this.parent(i);
}
});
var x = new childTest ();
x.test(“call”);
It won’t call the parent method. But if you move the “test” method to top (before the “testTwo”) in the parent class, it runs as expected.
Thanks for the feedback!
You are right, the bug is there, I have made a Fiddle about it: http://jsfiddle.net/lzzluca/2kj7jg4p/
I am very surprised, going to check it!
I fixed it with some dirty hack. And also added Event and Options implementation.
http://jsfiddle.net/AQ2uR/28/
This Class implementation saved my life. Again I would like to thank you for it! Following you for new releases ๐
Thanks a lot for your work! I have quickly checked the code, not sure I have understood the changes but I am going to be back to it soon!
And I am very happy you have found the Class implementation helpful!!! ๐
Inheritance problem: http://jsfiddle.net/AQ2uR/27/
Thanks for the Fiddle!
I have made a Fiddle myself before have seen this nice post, so nevermind about it!
Ok, now fixed, as you can see here: http://jsfiddle.net/lzzluca/2kj7jg4p/
Not sure what fixed it: the last commit shouldn’t be related but I didn’t have enough time to investigate.
I wonder if it was a JsFiddle bug, some weird thing happening with the js compilation, or something I am missed…