New Javascript Class with data hiding
4
Fordiman
Ok, so I was getting frustrated with the inability to hide data in javascript, as well as a number of other concerns with existing class structures (the requirement to use this.constructor in lieu of self, for example).
As a result, I've thrown together a nice little class constructor that accepts a class definition, as well as a class extender that allows you to build a new class from an existing one, with full access to all that private stuff.
As a result, I've thrown together a nice little class constructor that accepts a class definition, as well as a class extender that allows you to build a new class from an existing one, with full access to all that private stuff.
//First, the basic key to all this...
Function.prototype.setScope = function (scope) {
//Somone give a better way to do this; eval'ing is so
// bad for code quickness.
with (scope) { eval('var ret='+this.toString()); }
scope.member=ret;
return ret;
}
/* Compatibility function to stand in for
Prototype's highly useful bind function */
if (typeof Function.prototype.bind !== 'function')
Function.prototype.bind = function (context) {
var __method=this;
return function () {
return __method.apply(this,arguments);
}
}
function Class(def) {
var ext = function (dst,src,scope, context) {
//Specially tweaked cheapo Object.extend
if (typeof src=='undefined') return dst;
var tmpScope={};
for (var i in src)
if (typeof src[i]=='function' && typeof scope != 'undefined') {
tmpScope=ext({memberName:i},scope);
dst[i]=src[i].setScope(tmpScope);
if (typeof context=='object')
dst[i]=dst[i].bind(context);
} else
dst[i]=src[i];
return dst;
}
var instantiate;
var pSelf={};
var ret=function () {
instantiate.apply(this,arguments);
}
ret.classDef=def;
//Variables which will hold exposed scopes
var publicStatic={self:ret,scope:'public',type:'static'};
var privateStatic={self:ret,pSelf:pSelf,scope:'private',type:'static'};
with (publicStatic) {
instantiate=function () {
var i,pThis={};
var publicMember={
self:ret,scope:'public',
type:'instance'
};
var privateMember={
self:ret,pSelf:pSelf,pThis:pThis,
scope:'private',type:'instance'
};
if (typeof def.members == 'object') {
ext(this,def.members.blind,publicMember);
ext(this,def.members.privelidged,privateMember);
ext(pThis,def.members.private,privateMember);
if (!!this.initialize)
this.initialize.apply(this,arguments);
}
}
}
if (!!def.static) {
for (i in def.static.blind) {
console.log(i);
}
ext(ret,def.static.blind,publicStatic);
ext(ret,def.static.privelidged,privateStatic);
ext(pSelf,def.static.private,privateStatic);
}
return ret;
}
// Class extend. Takes the previously stored
// classDef, extends it with newDef, and creates a new Class
Class.extend = function (base,newDef) {
var defs=[base.classDef,newDef];
var combDef={};
var mode,scope;
for (i=0; i<defs.length; i++) {
for (mode in defs[i]) {
if (typeof defs[i][mode]=='object' && typeof combDef[mode]!=='undefined') {
combDef[mode]={};
for (scope in defs[i][mode])
combDef[mode][scope]=defs[i][mode][scope];
} else
combDef[mode]=defs[i][mode];
}
}
return Class(combDef);
}
; I hope that means something to someone. Meanwhile, here's a quick testsuite.
/* Quick and dirty compat function to get output
out regardless of interpreter */
if (typeof console=='undefined') {
var console={};
if (typeof WScript != 'undefined') {
console.log = function (a) {
WScript.Echo(a);
}
}else{
console.log=function (a) {
alert(a);
}
}
}
/* Test class, using one of each type of member */
var X = Class({
members: {
//Defined within constructor,
blind: {
//given class (self), instance (this)
//accessible as instance.member
//accessible to instance as this.member
cantSee: function () {
//Blind-scope members are for public
//instance variables and the functions that
//mess with only them
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis!='undefined'?"":"'t")+
" see private members"
);
}
},
privelidged: {
//given class (self), instance (this),
//class private (pSelf), instance private (pThis)
//accessible as instance.member
//accessible to instance as this.member
//Note that collisions are possible between blind
//and privelidged members, but not private
initialize: function () {
this.cantSee();
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis.inHiding!='undefined'?"":"'t")+
" see private members"
);
pThis.inHiding();
}
},
private: {
//given class (self), instance (this),
//class private (pSelf), instance private (pThis)
//accessible to instance as pThis.member
//should not exist as far as DOM tree is concerned
inHiding: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis.inHiding!='undefined'?"":"'t")+
" see private members"
);
self.cantSee();
}
}
},
static: {
blind: {
//given class (self)
//accessible as className.member
//accessible to self as self.member
cantSee: function () {
//Probably best to stick publicly useful utility
//functions and class constants here
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf!='undefined'?"":"'t")+
" see private statics"
);
self.canSee();
}
},
privelidged: {
//given class (self), class private (pSelf)
//accessible as className.member
//accessible to self as self.member
//Note that collisions are possible between blind
//and privelidged statics, but not private
canSee: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
pSelf.inHiding();
}
},
private: {
//given class (self), class private (pSelf)
//accessible to self as pSelf.member
//should not exist as far as DOM tree is concerned
inHiding: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log('Test ends');
}
}
}
});
//Call the initializer!
var x = new X();
out regardless of interpreter */
if (typeof console=='undefined') {
var console={};
if (typeof WScript != 'undefined') {
console.log = function (a) {
WScript.Echo(a);
}
}else{
console.log=function (a) {
alert(a);
}
}
}
/* Test class, using one of each type of member */
var X = Class({
members: {
//Defined within constructor,
blind: {
//given class (self), instance (this)
//accessible as instance.member
//accessible to instance as this.member
cantSee: function () {
//Blind-scope members are for public
//instance variables and the functions that
//mess with only them
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis!='undefined'?"":"'t")+
" see private members"
);
}
},
privelidged: {
//given class (self), instance (this),
//class private (pSelf), instance private (pThis)
//accessible as instance.member
//accessible to instance as this.member
//Note that collisions are possible between blind
//and privelidged members, but not private
initialize: function () {
this.cantSee();
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis.inHiding!='undefined'?"":"'t")+
" see private members"
);
pThis.inHiding();
}
},
private: {
//given class (self), instance (this),
//class private (pSelf), instance private (pThis)
//accessible to instance as pThis.member
//should not exist as far as DOM tree is concerned
inHiding: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log(
name+": I can"+
(typeof this.cantSee!='undefined'?"":"'t")+
" see public members"
);
console.log(
name+": I can"+
(typeof pThis.inHiding!='undefined'?"":"'t")+
" see private members"
);
self.cantSee();
}
}
},
static: {
blind: {
//given class (self)
//accessible as className.member
//accessible to self as self.member
cantSee: function () {
//Probably best to stick publicly useful utility
//functions and class constants here
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf!='undefined'?"":"'t")+
" see private statics"
);
self.canSee();
}
},
privelidged: {
//given class (self), class private (pSelf)
//accessible as className.member
//accessible to self as self.member
//Note that collisions are possible between blind
//and privelidged statics, but not private
canSee: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
pSelf.inHiding();
}
},
private: {
//given class (self), class private (pSelf)
//accessible to self as pSelf.member
//should not exist as far as DOM tree is concerned
inHiding: function () {
var name='X'+(type=='instance'?'#':'::')+'('+scope+')'+memberName;
console.log(
name+": I can"+
(typeof self.cantSee!='undefined'?"":"'t")+
" see public statics"
);
console.log(
name+": I can"+
(typeof pSelf.inHiding!='undefined'?"":"'t")+
" see private statics"
);
console.log('Test ends');
}
}
}
});
//Call the initializer!
var x = new X();





All methods in your shiny new Classes can call upon the following variables, great for debugging:
scope: will be either 'public' or 'private'
type: will be either 'instance' or 'static'
member: reference to the actual function
memberName: name assigned to actual function
self: class to which your methods belong
private statics
pSelf: private static variables and methods
public instance members
this: the oldy but goody
private instance members
pThis: private instance variables and methods
Function.prototype.setScope = function () {
if (typeof this.scope=='undefined')
this.scope={};
(function () {
for (i=0; i<arguments.length; i++)
for (v in arguments[i]);
this.scope[v]=arguments[i][v];
}).apply(this,arguments);
with (this.scope) {
eval('var ret='+this.toString());
}
ret.scope=this.scope;
return ret;
}