Advanced JavaScript inheritance model providing genuine private members based on John Resig's Inheritance Class
https://npmjs.org/package/jclass
JClass is a lightweight and flexible JavaScript inheritance model providing genuine private
members using closures. It's applicable in nodejs and normal web browsers. JClass is
implemented using prototype
's and brings along multiple features like e.g. propper (and even
inheritance consistent)instanceof
calls. To come straight to the point:
var Animal = JClass.extend({
// the constructor method
init: function(name) {
// a public value
this.name = name;
},
// a public method
eat: function() {
return this.name + ' eats';
},
// a private method, indicated by 2 leading underscores
__behave: function() {
return this.name + ' behaves';
}
});
// inherit from 'Animal'
var Cat = Animal.extend({
init: function(name, color) {
// call 'init' of the super-class
this._super(name);
this.color = color;
// a private value, indicated by 2 leading underscores
this.__lives = 9;
},
// overwrite 'eat'
eat: function() {
var msg = this.__behave();
// 'this._super' references Animal's 'eat' method
return msg + ' because ' + this._super() + ' fish';
},
// getter for '__lives'
getLives: function() {
return this.__lives;
}
});
var simon = new Cat('simon', 'white');
console.log(simon instanceof Cat); // true
console.log(simon instanceof JClass); // true
console.log(simon.name); // 'simon'
console.log(simon.__behave()); // TypeError: Object has no method '__behave'
console.log(simon.eat()); // 'simon behaves because simon eats fish'
console.log(simon.__lives); // undefined
console.log(simon.getLives()); // 9
This little example shows only the basics. It's even possible to customize and optimize JClass for
the various needs of your code by using options
(e.g.
you can change the naming scheme of private members). Take a look at the examples
to get a picture of what is possible and how it's working.
For node (using npm):
npm install jclass
For web browsers:
<script src="/path/to/jclass.min.js"></script>
var Vehicle = JClass.extend({
init: function(type) {
this.type = type;
},
drive: function() {
return 'driving';
}
});
var Car = Vehicle.extend({
init: function(color) {
// call the super 'init'
this._super('car');
this.color = color;
},
drive: function() {
// call the super 'drive'
return this._super() + ' on 4 tires';
}
});
var myCar = new Car('red');
console.log(myCar.drive()); // 'driving on 4 tires'
var Vehicle = JClass.extend({
init: function(type, price) {
this.type = type;
// a private value, indicated by 2 leading underscores
// because 'privatePattern' is /__.*/ by default
this.__price = price
// a private value using the 'private object'
this.__.consumption = 'too much';
},
// a private method
__drive: function() {
return 'driving';
}
});
var Car = Vehicle.extend({
init: function(color, price) {
this._super('car', price);
this.color = color;
},
// a getter for 'this.__price'
getPrice: function() {
return this.__price;
},
forceDrive: function() {
return this.__drive();
}
});
var myCar = new Car('red', 10000);
console.log(myCar.__price); // undefined
console.log(myCar.getPrice()); // 10000
console.log(myCar.__.consumption); // undefined
console.log(myCar.__drive()); // TypeError: Object has no method '__drive'
console.log(myCar.forceDrive()); // 'driving'
var opts = {
tracking: false
};
var Vehicle = JClass.extend({
init: function(type, price) {
this.type = type;
// won't be private since there is no tracking
this.__price = price
// values of the 'private object' stay private though!
this.__.consumption = 'too much';
},
// still a private method since tracking does not affect methods
// (methods are kown before the first method call)
__drive: function() {
return 'driving';
}
}, opts); // note the second parameter
var Car = Vehicle.extend({
init: function(color, price) {
this._super('car', price);
this.color = color;
},
forceDrive: function() {
return this.__drive();
}
});
var myCar = new Car('red', 10000);
console.log(myCar.__price); // 10000
console.log(myCar.__drive()); // TypeError: Object has no method '__drive'
console.log(myCar.forceDrive()); // 'driving'
var opts = {
extendable: false
};
var Vehicle = JClass.extend({
init: function(type) {
this.type = type;
// a private value
this.__secret = 'yay!';
}
}, opts); // note the second parameter
var CarHack = Vehicle.extend({
init: function() {
this._super('car');
},
// try to get the '__secret'
getSecret: function() {
return this.__secret;
}
});
// 'CarHack' won't be assigned since 'Vehicle' cannot be subclassed anymore
var opts = {
ctorName : 'ctor', // the constructor name
superName : '_parent', // the super method name
privatePattern: '/^__.+__$/', // the naming scheme
privateName : '_private' // the name of the private object
};
var Vehicle = JClass.extend({
ctor: function(type) {
this.type = type;
// a private value
this.__secret__ = 'yay!';
// a private value using the 'private object'
this._private.foo = 'bar';
},
// a private method
__drive__: function() {
return 'driving';
}
});
var Car = Vehicle.extend({
ctor: function(color) {
// call the super 'init'
this._parent('car');
this.color = color;
},
tellSecrets: function() {
return this.__drive__() + ' ' + this.__secret__;
}
});
var myCar = new Car('red');
console.log(myCar.tellSecrets()); // 'driving yay!'
The signature of extend
is
var SubClass = JClass.extend(properties [, options]);
-
properties
- (Object, mandatory) - An object that defines the methods of your class. See John Resig's Simple Inheritance technique for more details. -
options
- (Object, optional) - An object containing-
extendable
- (Boolean, default: true) - When true, another subclass can inherit from this class. When false, this class cannot be subclassed (e.g. likefinal
classes in Java). -
ctorName
- (String, default: 'init') - The name of the method that is invoked when a new instance of your class is created vianew MyClass()
. All passed arguments are applied to this method (OO speaking: the constructor's name). -
superName
- (String, default: '_super') - The name of the methods of the super-class. When you call (e.g.)this._super()
in yourinit
method, theinit
method of the super-class is called. -
enablePrivacy
- (String, default: true) - When false, this is equivalent toprivatePattern = null
plusprivateName = null
(see below). -
privatePattern
- (RegExp, default: /^__.+/) - A regular expression that defines how your private members look like./^__.+/
would find__fooFn
but not_barFn
. Null means that there will be no private members. -
privateName
- (String, default: '__') - In addition to values/methods that match theprivatePattern
, another private object namedprivateName
is visible in every method's scope. In some cases, you may use this object instead of usingtracking
(see below). Null means that no additional private object will be used. -
tracking
- (Boolean, default: true) -tracking
means that all members of an instance are compared before and after a method call in order to track down any addition or deletion of private values (not methods!). When you call (e.g.) 'this.__foo = 123' inside a method, the key__foo
(that also matches theprivatePattern
) was obviously added and will be detected by the before/after comparison. If your classes have a lot of members, you should use the additional private object defined byprivateName
to reduce the number of comparisons. Note: when false, all non-methods (even if they match the 'privatePattern') won't be private (this.__foo
in our example). -
methodsKey
- (String, default: '_jcMethods_') - The name of the object that holds all private methods during a method call. Note: you only need to change this value in case of a name collision with your code. -
depthKey
- (String, default: '_jcDepth_') - The name of the depth value (0 for JClass, 1 for the first derived class, 2 for the second ...). Note: you only need to change this value in case of a name collision with your code. -
callerDepthKey
- (String, default: '_jcCallerDepth_') - The name of the depth value of the initial caller. Note: you only need to change this value in case of a name collision with your code.
-
Please note: when extending an existing class, please use identical values for the options
ctorName
, superName
, privatePattern
and privateName
in order to keep the prototype/inheritence model
consistent!
Use this method when JClass
interfers with your code. It returns a reference to the JClass
base
object and resets the JClass
variable to its initial value.
- Source hosted at GitHub
- Report issues, questions, feature requests on GitHub Issues
Marcel R. (riga)