


ES5-Class
A Class object that enables native prototypal inheritance for Node and modern browsers.
It's called class because it encapsulate your methods, provide inheritance, set static and prototype methods and variables,
and provide helper functions along all your instances.
Why should we write code like if we were in 2010? Read on!
- Inheritance made easy
- It's freaking fast, check the benchmark section
- Uses
Object.setPrototypeOf
(when available, using __proto__
when isn't), Object.create
and Object.defineProperty
ES5/ES6 methods to enable native prototypal inheritance with proper settings (enumerable, configurable, writable) - Works with Node.js 0.8.x and up, and modern browsers.
- Functions to implement other class methods and include other instance/prototype methods
- The
$implement
method imports both prototype and class methods - The
$include
method imports prototype methods, and class methods as prototype - Takes advantage of ES5 non-writable properties to disable the possibility of messing up the classes
- Ability to inherit from multiple classes using arrays using
Class.$define('YourClass', [Class1, Class2, Class3])
without setting the $parent
class, working like a mixin - Inject and call
$super
to reach the parent instance class function or extended class method - Call
this.$parent
to reach the parent class definition - Inject mixin code (as plain objects, functions or other classes) using
$include
/$implement
- Extend static class methods and properties with
$implement
$implements
array property contain all classes that were implemented into the current class- The
construct
method is called with arguments when the class is instantiated $class
is available everywhere, it returns the current class, even before instantiation- You are free to instantiate your class using
Class.$create(arguments)
, Class(arguments)
and new Class(arguments)
Breaking changes
Version 1.x had this.$super
call, but it wasn't "async safe". If you would execute it in an asynchronous (setImmediate
,
setTimeout
, inside Promises or other callbacks), this.$super
could be either undefined or be another function, since
this.$super
was a global state variable on the instance.
To solve this, the $super
(idea taken from Prototype.js) must be injected as the first parameter on your function.
Before you'd call your $super
classes like this:
var Base = ES5Class.$define('Base', {
construct: function(that, those){
this.that = that;
this.those = those;
}
});
var Sub = Base.$define('Sub', {
construct: function(){
this.$super('that','those');
}
});
Sub.$create('those');
In version 2.x, you need to call it like:
var Base = ES5Class.$define('Base', {
construct: function(that, those){
this.that = that;
this.those = those;
}
});
var Sub = Base.$define('Sub', {
construct: function($super, those){
$super('that', those);
}
});
Sub.$create('those');
$super
is merely a shortcut for this.$parent.prototype.fn.apply(this, arguments);
(actually a bit fancier than that).
Nothing stops you from doing that by yourself (if you don't fancy the $super
argument)
In version 2.x you'll also need better-curry as a dependency.
Contributors
Install
$ npm install es5class
$ bower install es5class
var ES5Class = require('es5class');
window.ES5Class
define(['ES5Class'], function(ES5Class){
});
Documentation
See docs in ES5Class Documentation
Example usage
Creating a new class
var Animal = Class.$define(
'Animal',
{
construct: function (name){
this.name = name;
},
getName : function (){
return this.name;
}
},
{
count : 0,
getCount: function (){
return this.count;
}
}
);
Class inheritance
var Bird = Animal.$define('Bird', {
construct: function ($super, name, canFly){
if (canFly) {
this.canFly = canFly;
}
$super(name + ' Bird');
},
canFly : false
});
Extending a prototype
Bird.$include({
fly: function (){
if (this.canFly) {
console.log(this.name + ' flies!');
} else {
console.log(this.name + ' cant fly');
}
}
});
Implement
var
Class1 = Class.$define('Class1'),
obj = {yup: true},
h = function(){};
h.prototype.nope = false;
Class1.$implement([obj, h]);
console.log(Class1.yup);
console.log(Class1.$create().nope);
You can all the inheriting class construct by passing the second parameter, for example:
var EventEmitter = require('events').EventEmitter;
Class.$define('MyEventEmitter', function(){
this.$implement(EventEmitter);
return {
construct: function(){
EventEmitter.call(this);
}
};
});
Class.$define('MyEventEmitter').$implement(EventEmitter, true);
Because it's really easy to forget to initialize the inheriting class
Include
var
Class1 = Class.$define('Class1'),
obj = {yup: true},
h = function(){};
h.prototype.nope = false;
h.yep = false;
Class1.$include([obj, h]);
console.log(Class1.$create().yup);
console.log(Class1.nope);
console.log(Class1.$create().nope);
console.log(Class1.$create().yep);
Inherit from any existing Node.js class
var MyEventClass = Class.$define('MyEventEmitter', function(){
var base = this;
base.$implement(require('events').EventEmitter);
return {
construct: function(){
var self = this;
process.nextTick(function(){
self.emit('created', base);
});
}
};
});
MyEventClass.$create().on('created', function(base){
expect(base).to.eql(MyEventClass);
expect(base.prototype.on).to.be.a('function');
});
Encapsulate logic by passing a closure
Bird.$include(function (_super){
var timesBeaked = 0;
return {
beak: function (){
return ++timesBeaked;
}
};
});
Bird.$implement(function (_super){
var catalog = {};
return {
catalog: function (bird){
if (arguments.length) {
for(var i = 0; i < arguments.length; i++) {
catalog[arguments[i].name] = arguments[i];
}
}
return catalog;
}
};
});
Extending a class
Animal.$implement({
run: function() {
for(var i=1; i<=10; i++) {
this.ran++;
console.log("Animal ran for " + i + " miles!");
}
},
ran: 0
});
var Dog = Animal.$define('Dog');
Animal.run();
var Cat = Animal.$define('Cat');
Cat.run();
Dog.run();
Dog.run();
Dog.$implement({
run: function(){
this.ran += 10;
this.$parent.run();
}
});
Dog.run();
Creating an instance
var animal = Animal.$create("An Animal");
var bird = Bird.$create("A Bird");
var bird2 = Bird("Another bird");
var bird3 = new Bird("Also a bird");
Checking instances
animal.$instanceOf(Animal);
animal.$instanceOf(Bird);
bird.$instanceOf(Animal);
bird.$instanceOf(Bird);
bird.$instanceOf(Class);
Other useful methods and properties
Animal.$className;
bird.$class;
bird.$class.$className
bird.$class.$parent.$className
bird.$parent.$className
bird.$parent.$parent.$className
bird.$isClass(Bird);
Animal.$isClass(Bird);
Mixin from other classes
var Class1 = Class.$define('Class1', {}, {done: true}),
Class2 = Class.$define('Class2', {func: function(){ return true; }}),
Class3 = Class.$define('Class3', {}, {yet: true});
var NewClass = Class.$define('NewClass', {}, [Class1, Class2, Class3]);
Class1.done = false;
console.log(NewClass.done);
console.log(NewClass.yet);
console.log(NewClass.$parent);
console.log(NewClass.$implements);
console.log(NewClass.$create().func());
console.log(NewClass.$create().$class.done);
NewClass = Class.$define('NewClass', [Class1, Class2, Class3]);
console.log(NewClass.$create().yet);
console.log(NewClass.$create().done);
console.log(NewClass.$create().func);
Singletons
var Singleton = Class.$define('Singleton', {}, {
staticHelper: function(){
return 'helper';
},
staticVariable: 1
});
var ExtraSingleton = Class.$define('Extra');
ExtraSingleton.$implement(Singleton);
ExtraSingleton.$implement({
extra: true
});
ExtraSingleton.staticHelper()
Singleton.extra
ExtraSingleton.extra
ExtraSingleton.staticVariable
Share data between instances (flyweight pattern)
var Share = Class.$define('Share', function(){
var _data = {};
return {
construct: function(){
this.$class.count++;
},
append: function(name, data){
_data[name] = data;
}
}
}, {
count: 0
});
var one = Share.$create('one'), two = Share.$create('two');
one.append('dub', true);
two.append('dub', false);
two.append('bleh', [1,2,3]);
Duck typing (nothing stops you to not using inheritance and decoupling classes)
var Op = Class.$define('Op', {
construct: function (number){
this.number = number;
},
operator : function (number){
return number;
}
});
var Mul = Op.$define('Multiply', {
operator: function (number){
return number * this.number;
}
});
var Div = Op.$define('Divide', {
operator: function (number){
return number / this.number;
}
});
var Sum = Op.$define('Sum', {
operator: function (number){
return number + this.number;
}
});
var Operation = Class.$define('Operation', {}, function (){
var
classes = [],
number = 0;
return {
add : function (clss){
for (var i = 0, len = clss.length; i < len; i++) {
classes.push(clss[i]);
}
return this;
},
number : function (nmbr){
number = nmbr;
return this;
},
result : function (){
var result = number;
for (var i = 0, len = classes.length; i < len; i++) {
result = classes[i].operator(result);
}
return result;
},
onthefly: function (classes){
var result = number;
for (var i = 0, len = classes.length; i < len; i++) {
result = classes[i].operator(result);
}
return result;
}
};
});
var sum = Sum.$create(40);
var mul = Mul.$create(50);
var div = Div.$create(20);
Operation.number(100);
Operation.add([sum, mul, div]).result();
var mul2 = Mul.$create(30);
Operation.onthefly([div, sum, mul, mul2]);
For a lot of class examples (inheritance, extending, singletons, etc), check the test sources at test/class-test.js
Performance tip
Although the class helpers, $super
calls, class inheritance itself are fast, $define
'ing your class isn't.
For some odd reason, Object.defineProperties
and Object.defineProperty
is long known to have the worst performance in V8
(and other engines as well).
Basically, you should never keep redefining your class, for example, in loops, inside other constructors, etc.
The ES5Class.$define
is a real bottleneck (as low as 10k ops/s). So, $define
it once, create instances everywhere!
Running the tests
The tests are ran using mocha
$ npm install && npm run test
Benchmark
Check how this library perform on your machine
$ npm install && npm run benchmark
A benchmark result in a 1st gen Core i3:
class instance function call x 114,660,575 ops/sec ±7.30% (32 runs sampled)
class method function call x 118,824,558 ops/sec ±4.74% (57 runs sampled)
class instance included function call x 98,900,288 ops/sec ±3.71% (56 runs sampled)
$super instance function calls x 14,078,465 ops/sec ±0.55% (97 runs sampled)
$super class function calls x 13,880,657 ops/sec ±0.43% (99 runs sampled)
$super inherited two levels deep function calls x 4,664,890 ops/sec ±0.51% (96 runs sampled)
class.$create instantiation x 1,617,001 ops/sec ±1.60% (90 runs sampled)
new operator x 2,493,494 ops/sec ±0.32% (101 runs sampled)
obj() instance x 1,076,858 ops/sec ±0.88% (91 runs sampled)
Class definition x 12,307 ops/sec ±2.76% (83 runs sampled)
Feeback
Please use the issues section of github to report any bug you may find
License
(The MIT License)
Copyright (c) 2011 Bruno Filippone
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.