Drake Amara
Drake Amara

Reputation: 3312

How to Create Protected Object Properties in JavaScript

Is there a JavaScript pattern which mimics "Protected" object properties like what you see in languages like C++ ??

Basically, I'd like to create an Object A which has a number of "protected" object properties which can be accessed ONLY from methods which are defined from the prototype of Object A. i.e. - NOT accessible publicly from non-prototyped methods of A.

For instance, ideally would be like so:

function A(){
    var prop1 = 1;      
}

A.prototype.myFunc = function(){
    var newVar = this.prop1;   //newVar now is equivalent to 1
}

var instanceOfA = new A();
var newVar2 = instanceOfA.prop1;  //error given as prop1 is "protected"; hence undefined in this case

BTW - I do not want the pattern of privileged member functions accessing private properties since the member function is still public.

Upvotes: 13

Views: 16198

Answers (8)

Thomas Eding
Thomas Eding

Reputation: 1

You cannot do it in Javascript.

Upvotes: 9

Steve Jorgensen
Steve Jorgensen

Reputation: 12341

There is a pattern that I have come to like that does not work the same way as protected access does in most languages, but provides a similar benefit.

Basically, use a builder method to create a closure for properties, and then have that method create a "full" object with liberal access as well as an "exposed" object with more limited access. Place the exposed object into a property of the full object, and return that full object to the caller.

The caller can then make use of the full object (and pass that to other appropriate collaborators), but provide only the exposed object to collaborators that should have the more restricted access.

A contrived example…

// Ring employs a typical private/public pattern while
// RingEntry employs a private/exposed/full access pattern.

function buildRing( size ) {
  var i
    , head = buildRingEntry( 0 )
    , newEntry;
  ;
  head.setNext( head );
  for( i = size - 1; i ; i-- ) {
    newEntry = buildRingEntry( i );
    newEntry.setNext( head.getNext() );
    head.setNext( newEntry );
  }
  function getHead() { return head.exposed; }
  return {
      getHead : getHead
  }
}

function buildRingEntry( index ) {
  var next
    , exposed
  ;
  function getIndex() { return index; }
  function setNext( newNext ) { next = newNext; }
  function getNextFullEntry() { return next; }
  function getNextExposedEntry() { return next.exposed; }
  exposed = {
      getIndex : getIndex
    , getNext  : getNextExposedEntry
  };
  return {
      getIndex : getIndex
    , setNext  : setNext
    , getNext  : getNextFullEntry
    , exposed  : exposed
  };
}

If we use that to build a ring of 4 entries ring = buildRing(4);, then ring.getHead().getIndex() gives us 0, ring.getHead().getNext().getIndex() gives us 1, ring.getHead().getNext().getNext().getIndex() gives us 2, etc.

If we try to execute ring.getHead().setNext({}) or ring.getHead().getNext().setNext({}), however, we get an error because setNext is not a property of an exposed entry object.

Caveat:

Since this is in the family of patterns that build the methods again in a new closure for each new object, it is not suitable for situations in which a very high volume of instantiation may be needed.

Upvotes: 1

Finickyflame
Finickyflame

Reputation: 863

I was interested to find a way to answer your question, and here's what I was able to do.

You'll need this helper:

var ProtectedHandler = (function () {
    /// <Sumarry>
    /// Tool to handle the protected members of each inheritance.
    /// </Summary>
    /// <param name="current">Current protected variable.</param>
    /// <param name="args">The arguments variable of the object.</param>
    /// <param name="callback">The function to initialise the variable in the 'object'.</param>
    /// <param name="isParent">Is this the ultimate base object.</param>
    function ProtectedHandler(current, args, callback, isParent) {
        this.child = getChild(args);
        if (callback)
            this.callback = callback;

        if (isParent)
            this.overrideChild(current);
    }

    // Get the ProtectedHandler from the arguments
    var getChild = function (args) {
        var child = null;
        if (args.length > 0 && (child = args[args.length - 1]) && child.constructor === ProtectedHandler)
            return child;
    };

    // Chain Initialise the protected variable of the object and its inheritances.
    ProtectedHandler.prototype.overrideChild = function (newValue) {
        if (this.callback != null) {
            this.callback(newValue);
        }
        if (this.child != null) {
            this.child.overrideChild(newValue);
        }
    };

    // Static function to create a new instance of the protectedHandler object.
    ProtectedHandler.handle = function (protected, arguments, callback, isParent) {
        return new ProtectedHandler(protected, arguments, callback, isParent);
    };

    return ProtectedHandler;
})();

This helper will allow you to handle multiple inheritances. The trick is to copy the protected variable from the base object to your new object (child).

To prove you it's working, here's an example:

// That's the default extends function from typescript (ref: http://www.typescriptlang.org/)
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};

var BaseClass = (function () {        
    function BaseClass() {
        // Members
        var private = {},
            protected = {},
            public = this;

        // Constructor
        ProtectedHandler.handle(protected, arguments, function () {
            protected.type = "BaseClass";
        }, true);

        // Methods
        protected.saySomething = function () {
            return "Hello World";
        };

        public.getType = function () {
            return protected.type;
        };
    }

    return BaseClass;
})();



var Person = (function (_super) {
    __extends(Person, _super);

    function Person(name) {
        // Members
        var private = {},
            protected = {},
            public;

        // Constructor
        _super.call(public = this, ProtectedHandler.handle(protected, arguments, function (p) {
            protected = p; //This is required to copy the object from its base object.
            protected.name = name;
            protected.type = "Person";
        }));

        //Method
        public.getName = function () {
            return protected.name;
        };

        public.saySomething = function () {
            return protected.saySomething();
        };
    }
    return Person;
})(BaseClass);


var Child = (function (_super) {
    __extends(Child, _super);

    function Child(name) {
        // Members
        var private = {},
            protected = {},
            public;

        // Constructor
        _super.call(public = this, name, ProtectedHandler.handle(protected, arguments, function (p) {
            protected = p; //This is required to copy the object from its base object.
            protected.type = "Child";
        }));

        //Method
        public.setName = function (value) {
            return protected.name = value;
        };
    }
    return Child;
})(Person);

And here's the tests:

var testBase = new BaseClass();
testBase.getType(); //"BaseClass"
testBase.saySomething; //undefined

var testPerson = new Person("Nic");
testPerson.getType(); //"Person"
testPerson.saySomething(); //"Hello World"
testPerson.name; //undefined
testPerson.getName() //"Nic"
testPerson.setName; //undefined

var testChild = new Child("Bob");
testChild.getType(); //"Child"
testChild.saySomething(); //"Hello World"
testChild.name; //undefined
testChild.getName(); //"Bob"
testChild.setName("George");
testChild.getName(); //"George"

Upvotes: 1

Martin Wantke
Martin Wantke

Reputation: 4571

function ClassA(init)
{
    var protected = {};
    protected.prop = init * 10;
    if(this.constructor != ClassA) { return protected; }
}

function ClassB()
{
    var protected = ClassA.call(this, 5); //console.log(protected.prop);
}

//var a = new ClassA(123);
//var b = new ClassB();

Upvotes: 2

Martin Wantke
Martin Wantke

Reputation: 4571

I found a way for creating protected members. Therefor I call the base constructor and return an object with the protected members at the same time:

var protected = BaseClass.call(this); 

Here an example:

function SignedIntegerArray(size)
{
    var public = this;
    var protected = {};

    // private property:
    var _maxSize = 10000;
    // protected property:
    protected.array = [];
    // public property:
    public.Length = size;

    if(!isInteger(size) || size < 0 || size > _maxSize) { throw "argument exception"; }
    for(var index = 0; index != size; index++) { protected.array[index] = 0; }

    // private method:
    function isInteger(i) { return i == i + 0 && i == ~~i; }
    // protected method:
    protected.checkIndex = function(index) { return index >= 0 && index < size; }
    // public methods:
    public.SetValue = function(index, value) { if(protected.checkIndex(index) && isInteger(value)) { protected.array[index] = value; } };
    public.GetValue = function(index) { if(protected.checkIndex(index)) { return protected.array[index]; } else { throw "index out of range exception"; }}

    return protected;
}

function FloatArray(size, range)
{
    var public = this;
    var protected = SignedIntegerArray.call(this, size); // call the base constructor and get the protected members 

    // new private method, "isInteger" is hidden...
    function isFloat(argument) { return argument != ~~argument; }
    // ...but "checkIndex" is accessible
    public.SetValue = function(index, value) { if(protected.checkIndex(index) && isFloat(value) && value >= public.MinValue && value <= public.MaxValue) { protected.array[index] = value; } };

    // new public properties:
    public.MinValue = -range;
    public.MaxValue = range;

    return protected; // for sub-classes
}

function newObject(className, args) { return new function() { className.apply(this, args)}} // you need to use function.call or function.apply to initialize an object. otherwise the protected-object is empty.
window.addEventListener("load", function()
{
    var o = newObject(FloatArray, [4, 50.0]);
    o.SetValue(3, 2.1);
    console.log(o.GetValue(3));
    console.log(o.Length); // property from the base-class
});

Upvotes: 5

Roman
Roman

Reputation: 1

Take a look at workaround proposed by Maks on his website: Emulating protected members in JavaScript

It emulates protected access level to methods and properties of an object.

Upvotes: 0

jfriend00
jfriend00

Reputation: 707436

There is no object property that can only be accessed from prototyped methods of A and not from non-prototyped methods of A. The language doesn't have that type of feature and I'm not aware of any work-around/hack to implement it.

Using Doug Crockford's methods, you can create member properties that can only be accessed from predefined non-prototyped methods (those defined in the constructor). So, if you're trying to limit access only to a predefined set of methods, this will accomplish that. Other than that, I think you're out of luck.

If you want other ideas, you'd probably get more help if you describe more about what you're actually trying to accomplish in your code rather than just how to emulate a feature in another language. Javascript is so much different than C++ that it's better to start from the needs of the problem rather than try to find an analogy to some C++ feature.

Upvotes: 12

Anony372
Anony372

Reputation: 494

This is probably what you're looking for: http://javascript.crockford.com/private.html

Upvotes: 4

Related Questions