Skip to content

Repository files navigation

Javascript-FastClass

A faster and easier way to define Javascript Prototypal Inheritance: classes and mixins

Node:Nuget:
npm install fast-class Install-Package Javascript-FastClass

Performance tests

Syntactic sugar

varVehicle=Function.define(function(name)){//define the "base class"this.name=name;//constructor initializer},{//optionally specify prototype members i.e. to be added on Vechicle.prototypedraw: function(){console.log("Drawing a "+this.name+" vehicle.");}});//optionally specify extra mixins i.e. , Wheels);varCar=Vechicle.inheritWith(function(base,baseCtor)){//define the "derrived class"return{//optionally specify prototype membersconstructor: function(name,color){//optionally specify a custom construcorbaseCtor.call(this,name);//ensure you are calling the baseCtorthis.color=color;},draw: function(){//redefine the draw function on Car.prototypeconsole.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 mixinfunctionEngine(){//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 instancethis.cmc=1400;// i.e. var c= new Car(); c.cmc = 1400this.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 mixinisElectric: function(){returnthis.pwerSource==='electric';}})vartoyota=newCar("toyota prius","red");//creating a new Cartoyota.powerSouce;//petroltoyota.powerSource='electric';//changing the powerSource property toyota.isElectric();//returns truetoyota.draw();//Drawing a red car. //Drawing a tyota prius vechicle.

Why yet another library?

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?

When to use it?

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.

What is it?

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(base,baseCtor){this.somePrototypeMethod1= ...;this.somePrototypeMethod2= ...;}}

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

How to use it?

Defining the base "class"

varFigure=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);}

.define sugar

You can define the first class' constructor function (same as above but with sugar syntax) as following:

varA=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)

Function.define even better sugar - recommended

This way we don't need to call Function.initMixins(this) as it will be automatically called for us

varA=Function.define(function(name){this.name=name;},{draw: function(){//method added on function(name) {}.prototypeconsole.log("figure "+this.name);}}

Alternatively you can pass an object with the constructor function specified (similar syntax to .inheritWith)

varA=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.prototypeconsole.log("figure "+this.name);}}

All of the above methods are doing the same thing. You decide which one better suits you.

Inheritance

A classical example to use inheritance when you have a base class called Figure and a derived class called Square.

.fastClass flavour

To define the derrived class Square:

varSquare=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 functionconsole.log("square with length "+this.length);base.draw.call(this);//calls the base class' draw function}})

.inheritWith flavour

To define the derrived class Square:

varSquare=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 functionconsole.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)

The constructor

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.

Usage

Whichever flavour you choose the code usage is the same. Firstly you need to instantiate the Constructor with the new operator:

varfigure=newFigure("generic");figure.draw();//figure genericvarsquare=newSquare("10cm square",10);figure.draw();//square with length 10//figure 10cm square

Mixins support

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 the class.prototype that are using it

Another Example

// Animal base classfunctionAnimal(){// Privatefunctionprivate1(){}functionprivate2(){}// Privileged - on instancethis.privileged1=function(){}this.privileged2=function(){}Function.initMixins(this);}.define({// Public - on prototypemethod1: function(){console.log("Animal::method1");}});

Alternatively the above can be defined as:

varAnimal=Function.define(function(){// Private functionprivate1(){}functionprivate2(){}// Privileged - on instancethis.privileged1=function(){}this.privileged2=function(){}},{method1: function(){console.log("Animal::method1");}};});

Or even simpler as an object providing the constructor:

varAnimal=Function.define({constructor: function(){// the constructor is optional// Private functionprivate1(){}functionprivate2(){}// Privileged - on instancethis.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:

varanimal=newAnimal();// Create a new Animal instance

Spicing it up with inheritance and mixins

We can typically define a move action which is a set of methods grouped in our Move mixin

Defining the mixin

functionMove(){//define the constructor of the mixin. This will be automatically called for every instance in the constructor of the class that is using this mixinthis.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);}});

Adding Inheritance and using the Move mixin

Usage using .inheritWith flavour

[[constructor]].inheritWith( function(base, baseCtor) { return {...} }, mixins ) - that function should return methods for the derrived prototype

Example

// Extend the Animal class with inheritWith flavorvarDog=Animal.inheritWith(function(base,baseCtor){//derrived class containing some private method(s)functionsomeOtherPrivateMethod(){}return{// Override base class `method1`method1: function(){someOtherPrivateMethod.call(this);base.method1.call(this);//calling a methd from the base class: Animal.prtotype.method1console.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:

varhusky=newDog();husky.method1();// Animal::method1// Dog::method1husky.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}

Same as above using .fastClass flavour

[[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 flavorvarDog=Animal.fastClass(function(base,baseCtor){//private functionsfunctionsomeOtherPrivateMethod(){};//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 functionbase.method1.call(this);console.log('Dog::method1');};this.scare=function(){console.log('Dog::I scare you');};//some more public functionsthis.method2=function(){};},Move);

Static members

You can sometime extend a constructor or a function with some static methods In order to do so you need to call .defineStatic({...})

varF=function(){}.defineStatic({loadData: function(){...}}).typeofF.loadData// returns functionF.loadData()//call the static function

This can be easily mixed with Function.define

varF=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 hereloadData: function(){...}};typeofF.loadData// returns functionF.loadData()//call the static function

Mixin abstract methods

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

varAnimal=Function.define({method1: function(){thrownewException("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});

Abstract methods sugar

varf=Function.abstract();f();//will trigger a failed assert with message "Not implemented"varg=Function.abstract("Custom not implemented message");g();//will trigger a failed assert with message "Custom not implemented message"f.abstract===true;//truevarh=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"varj=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"

Where to get it from?

Beside GitHub, you can download it as a Nuget package in Visual Studio fromhere

Install-PackageJavascript-FastClass

There is also a nodejs version

npminstallfast-class

What's next?

Do you have a better & faster way? Share it! We would love to seeing creativity in action!

githalytics.com alpha

About

Fast and syntactic sugar Javascript Inheritance

Resources

Stars

33 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages