New Javascript Class with data hiding





4
Date Submitted Mon. Dec. 24th, 2007 2:34 AM
Revision 1 of 1
Helper Fordiman
Tags "data hidng" | Class | JavaScript | Prototype
Comments 3 comments
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.

//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();

Bryan Elliott

Comments

Comments Addendums
Mon. Dec. 24th, 2007 2:42 AM    Helper Fordiman
Comments expanded setScope
Mon. Dec. 24th, 2007 3:29 AM    Helper Fordiman
  Comments Edit:
Mon. Dec. 24th, 2007 3:35 AM    Helper Fordiman

Voting

Votes Down