A faster and easier way to define Javascript Prototypal Inheritance: classes
and mixins
Node: | Nuget: |
---|---|
npm install fast-class | Install-Package Javascript-FastClass |
- Among
popular libraries define + usage
- 3 classes and 3 methods * 500 instances each Fastest libraries define + usage
- 3 classes and 3 methods * 500 instances eachFastest libraries define only
- 3 classes and 3 methods * 1 instances each- Unit tests - so you can see it just works
var Vehicle = Function.define(function(name)){//define the "base class"
this.name = name;//constructor initializer
}, { //optionally specify prototype members i.e. to be added on Vechicle.prototype
draw: function() { console.log("Drawing a " + this.name + " vehicle."); }
});//optionally specify extra mixins i.e. , Wheels);
var Car = Vechicle.inheritWith(function(base, baseCtor)){//define the "derrived class"
return { //optionally specify prototype members
constructor: function(name, color) { //optionally specify a custom construcor
baseCtor.call(this, name);//ensure you are calling the baseCtor
this.color = color;
},
draw: function() { //redefine the draw function on Car.prototype
console.log("Drawing a "+this.color+" car.");
base.draw.call(this);//optionally call the base class' draw method
}
}
}, Engine);//optionally specify extra mixins whose prototype members will be copied to Car.prototype
//Define the Engine mixin
function Engine() {
//this constructor will be automatically called when creating any class using it
//(including derrived classes). i.e. new Car() in this example
this.powerSource = 'petrol';//these properties will be added to every Car instance
this.cmc = 1400;// i.e. var c= new Car(); c.cmc = 1400
this.horsePower = 140;
}.define({//optioanlly add custom methods on Engine.prototype mixin
//these will be copied to Car.prototype in our example, baecause it references the Engine mixin
isElectric: function() { return this.pwerSource === 'electric'; }
})
var toyota = new Car("toyota prius", "red");//creating a new Car
toyota.powerSouce; //petrol
toyota.powerSource = 'electric'; //changing the powerSource property
toyota.isElectric();//returns true
toyota.draw();
//Drawing a red car.
//Drawing a tyota prius vechicle.
Native javascript inheritance is a pin in the ass. Even if you understand it perfectly it still requires some hideous repetitive code.
There are a lot of libraries which aim to help you with such that, but the main question is:
What is the fastest
vs most convenient
(also known as: with the most sugar
) to create Prototypal Inheritance
with?
You might want this library
- when you can't use a language that
compiles
into javascript code. - because it is fast
- you love
writing less - doing more!
e.g. Google Closure, TypeScript, Coffee Script etc.
FastClass is a very tiny library (~1KB minified and gzipped) that helps you quickly derive your classes
so to speak.
It comes in two flavours:
Function.prototype.fastClass(creator)
- sets theBase.prototype
to thecreator
function and then callsnew creator(this.prototype, this)
function(base, baseCtor) { this.somePrototypeMethod1 = ...; this.somePrototypeMethod2 = ...; } }
Function.prototype.inheritWith(creator)
- recommended returns aplain object
containing the members of the new prototype, including the constructor itself
It makes usage of proto on all new browsers (which makes it blazing fast) except Internet Explorer <= 10
and maybe other ancient browsers where it fallbacks to for (var key in obj)
statement.
Note __proto__
will become standard in ECMAScript 6
function(base, baseCtor) { return { somePrototypeMethod1: ..., somePrototypeMethod2: ...} }
whereas baseCtor
is the function we want to inherit and base is it's prototype (baseCtor.prototype
that is).
var Figure = function(name) {
this.name = name;
Function.initMixins(this);// since this is a top function, we need to call initMixins to make sure they will be initialized.
}
Figure.prototype.draw = function() { console.log("figure " + name); }
You can define the first class' constructor function
(same as above but with sugar syntax) as following:
var A = function(name) {
this.name = name;
Function.initMixins(this);// since this is a top function, we need to call initMixins to make sure they will be initialized.
}.define({
draw: function() {
console.log("figure "+ this.name);
}
}/*, add any mixins here */)
The define
function copies all the members of the returned object to the function.prorortpe
(A.prototype
) and returns it (A
)
This way we don't need to call Function.initMixins(this)
as it will be automatically called for us
var A = Function.define(function(name){
this.name = name;
}, {
draw: function() {//method added on function(name) {}.prototype
console.log("figure " + this.name);
}
}
Alternatively you can pass an object with the constructor function specified (similar syntax to .inheritWith
)
var A = Function.define({
constructor: function(name) {//the constructor itself can be even missing. If so we will add one for you!
this.name = name;
},
draw: function() {//method added on constructor.prototype
console.log("figure " + this.name);
}
}
All of the above methods are doing the same thing. You decide which one better suits you.
A classical example to use inheritance when you have a base class called Figure
and a derived class called Square
.
To define the derrived class
Square:
var Square = Figure.fastClass(function(base, baseCtor) {
this.constructor = function(name, length) {
this.length = length;
baseCtor.call(this, name);//calls the bacse ctor
}
this.draw = function() {//redefines the draw function
console.log("square with length " + this.length);
base.draw.call(this);//calls the base class' draw function
}
})
To define the derrived class
Square:
var Square = Figure.inheritWith(function(base, baseCtor) {
return {
constructor: function(name, length) {
this.length = length;
baseCtor.call(this, name);//calls the bacse ctor
},
draw: function() {//redefines the draw function
console.log("square with length " + this.length);
base.draw.call(this);//calls the base class' draw function
}
}
})
As you can see in both cases the definition is pretty simple and very similar.
However the .inheritWith
flavour comes with about 5-15% performance boost
depending on the actual browser and number of members.
That is because we are simply setting __proto__
with the BaseClass.prototype for those browsers who support it (all nowdays browsers except IE<=10
)
In both cases we the constructor
is the function that is returned as the derrived class' constructor
.
The constructor
is optional and we should only add it when we have some custom code as both functions will add it for us otherswise.
Whichever flavour you choose the code usage is the same. Firstly you need to instantiate the Constructor with the new
operator:
var figure = new Figure("generic");
figure.draw();
//figure generic
var square = new Square("10cm square", 10);
figure.draw();
//square with length 10
//figure 10cm square
Mixins are some grouped functionalities that you can add to a class
without inheritance
You can learn more about mixins
vs inheritance
on this post.
We can define mixins as
- an
object
containing functions - a
function
which will be executed for every instance of the class that is using it- The
prototype
of the function will be automatically copied to theclass.prototype
that are using it
- The
// Animal base class
function Animal() {
// Private
function private1(){}
function private2(){}
// Privileged - on instance
this.privileged1 = function(){}
this.privileged2 = function(){}
Function.initMixins(this);
}.define({
// Public - on prototype
method1: function() { console.log("Animal::method1"); }
});
Alternatively the above can be defined as:
var Animal = Function.define(function(){
// Private
function private1() {}
function private2() {}
// Privileged - on instance
this.privileged1 = function(){}
this.privileged2 = function(){}
}, {
method1: function() { console.log("Animal::method1"); }
};
});
Or even simpler as an object providing the constructor
:
var Animal = Function.define({
constructor: function(){ // the constructor is optional
// Private
function private1() {}
function private2() {}
// Privileged - on instance
this.privileged1 = function(){}
this.privileged2 = function(){}
},
method1: function() { console.log("Animal::method1"); }
});
The function Animal
is the function
given as first parameter.
It acts as the constructor, which is invoked when an instance is created:
var animal = new Animal(); // Create a new Animal instance
We can typically define a move
action which is a set of methods grouped in our Move mixin
function Move() {//define the constructor of the mixin. This will be automatically called for every instance in the constructor of the class that is using this mixin
this.position = { x: 0, y: 0};
}.define({//define mixin's prototype. These will be copied to the prototype of the classes that will use this `mixin`
moveTo: function(x, y) {
this.position.x = x;
this.position.y = y;
},
resetPosition: function() {
this.position.x = 0;
this.position.y = 0;
},
logPosition: function() {
console.log("Position: ", this.position);
}
});
[[constructor]].inheritWith( function(base, baseCtor) { return {...} }, mixins )
- that function should return
methods for the derrived prototype
// Extend the Animal class with inheritWith flavor
var Dog = Animal.inheritWith(function(base, baseCtor) {
//derrived class containing some private method(s)
function someOtherPrivateMethod() { }
return {
// Override base class `method1`
method1: function(){
someOtherPrivateMethod.call(this);
base.method1.call(this);//calling a methd from the base class: Animal.prtotype.method1
console.log('Dog::method1');
},
scare: function(){
console.log('Dog::I scare you');
},
//some new public method(s)
method2: function() { }
}
}, Move);//specify any extra mixins to be added to the Dog's prototype
Create an instance of Dog
:
var husky = new Dog();
husky.method1();
// Animal::method1
// Dog::method1
husky.scare();
// Dog::I scare you'
/// testing the mixin:
husky.logPosition(); // Call the method of the mixin.
// Position: {x: 0, y: 0}
husky.moveTo(10, 20);
husky.logPosition();
// Position: {x: 10, y: 20}
husky.resetPosition();
husky.logPosition();
// Position: {x: 0, y: 0}
[[constructor]].fastClass( function(base, baseCtor) { this.m = function {...} ... } )
- that function populates
the derrived prototype
Every class definition has access to the parent's prototype via the first argument passed into the function. The second argument is the base Class
itself (its constructor i.e. Animal
in our case):
// Same as above but Extend the Animal class using fastClass flavor
var Dog = Animal.fastClass(function(base, baseCtor) {
//private functions
function someOtherPrivateMethod() {};
//since we don't set this.constructor, automatically will be added one for us which will call the baseCtor with all the provided arguments
//this is importat so we can set the new prototype into it
// Override base class `method1`
this.method1 = function(){
someOtherPrivateMethod.call(this);
// Call the parent function
base.method1.call(this);
console.log('Dog::method1');
};
this.scare = function(){
console.log('Dog::I scare you');
};
//some more public functions
this.method2 = function() {};
}, Move);
You can sometime extend a constructor or a function with some static methods
In order to do so you need to call .defineStatic({...})
var F = function() {}.defineStatic({
loadData: function() {...}
}).
typeof F.loadData // returns function
F.loadData() //call the static function
This can be easily mixed with Function.define
var F = Function.define({
constructor: function() {}
).defineStatic({//make sure you are adding it here and not on the constructor function itself.
//that is because Function.define will automatically declare a function in order
//to ensure Function.initMixins gets automatically called
//add any static members here
loadData: function() {...}
};
typeof F.loadData // returns function
F.loadData() //call the static function
The Function.initMixins
will always check for duplicate members that are already defined in the object i.e. the class defines a member whith the same name, or another mixin defines it.
If such collision is found an exception will be thorwn when using the debug version of the library. Using the .min
file there is no error and the member will be overriden.
However in order to prevent this exception you can define a function as abstract with .defineStatic(obj)
:
var Animal = Function.define({
method1: function() {
throw new Exception("You need to implement method1 on your class's or mixin's prototype")
}.defineStatic({abstract: true});//make this function abstract so it can be overriden by mixins
});
var f = Function.abstract();
f();//will trigger a failed assert with message "Not implemented"
var g = Function.abstract("Custom not implemented message");
g();//will trigger a failed assert with message "Custom not implemented message"
f.abstract === true;//true
var h = Function.abstract(function() { /* will be called before the assert "Not implemented" will be thrown */});
h();//will call the given function and then will trigger a failed assert with message "Not implemented"
var j = Function.abstract("Custom not implemented message", function() { /* will be called before the assert "Not implemented" will be thrown */});
j();//will call the given function and then will trigger a failed assert with message "Custom not implemented message"
Beside GitHub, you can download it as a Nuget package
in Visual Studio fromhere
Install-Package Javascript-FastClass
There is also a nodejs version
npm install fast-class
Do you have a better & faster way? Share it! We would love to seeing creativity in action!