Private variables in inherited prototypes
Asked Answered
M

4

20

I think I have misunderstood how Javascript prototypal inheritance works. Specifically, the prototypes internal variables seem to be shared between multiple different sub-objects. It is easiest to illustrate with code:

var A = function()
{
  var internal = 0;
  this.increment = function()
  {
    return ++internal;
  };
};

var B = function() {};
// inherit from A
B.prototype = new A;

x = new B;
y = new B;

$('#hello').text(x.increment() + " - " + y.increment());​

This outputs 1 - 2 (test it on JSBin), while I fully expected the result to be 1 - 1, since I wanted two separate objects.

How can I make sure that the A object isn't shared object between multiple instances of B?

Update: This article highlights some of the issues:

The problem is that the scope each approach uses to create a private variable, which works fine, is also the closure, in action, that results in if you change a private variable for one object instance, it is being changed for all. I.e. it’s more like a private static property, than an actual private variable.

So, if you want to have something private, more like a non-public constant, any of the above approaches is good, but not for actual private variables. Private variables only work really well with singleton objects in JavaScript.

Solution: As per BGerrissen's answer, changing the declaration of B and leaving of the prototype works as intended:

var B = function() { A.apply(this, arguments); };
Monjan answered 1/9, 2010 at 10:35 Comment(0)
W
17

Private members are tricky using prototypical inheritance. For one, they cannot be inherited. You need to create private members in each individual constructor. You can do this by either applying the super constructor in the subclass or create a decorator.

Decorator example:

function internalDecorator(obj){
    var internal = 0;
    obj.increment = function(){
        return ++internal;
    }
} 

var A = function(){
    internalDecorator(this);
}
A.prototype = {public:function(){/*etc*/}}

var B = function(){
    internalDecorator(this);
}
B.prototype = new A(); // inherits 'public' but ALSO redundant private member code.

var a = new B(); // has it's own private members
var b = new B(); // has it's own private members

This is just a variation of the super constructor call, you can also achieve the same by calling the actual super constructor with .apply()

var B = function(){
    A.apply(this, arguments);
}

Now by applying inheritance through B.prototype = new A() you invoke needless constructor code from A. A way to avoid this is to use Douglas Crockfords beget method:

Object.beget = function(obj){
    var fn = function(){}
    fn.prototype = obj;
    return new fn(); // now only its prototype is cloned.
}

Which you use as follows:

B.prototype = Object.beget(A.prototype);

Of course, you can abandon inheritance altogether and make good use of decorators, at least where private members are needed.

Warfourd answered 1/9, 2010 at 11:4 Comment(0)
H
16

You need to forget the idea of classes. There isn't really such a thing in JavaScript as an 'instance of B'. There is only 'some object you obtained by calling the constructor function B'. An object has properties. Some are its "own" properties, others are included by searching the prototype chain.

When you say new A, you're creating one object. Then you assign it as the prototype for B, which means that every call to new B produces a new object that has the same direct prototype, and hence the same counter variable.

In Tim Down's answer, two alternatives are shown. His incrementPublic uses inheritance, but makes the counter variable public (i.e. gives up encapsulation). Whereas incrementInternal makes the counter private but succeeds by moving the code into B (i.e. gives up inheritance).

What you want is a combination of three things:

  • inheritable behaviour - so it must be defined in A and require no code in B aside from setting the prototype
  • private data, stored in closure-local variables
  • per-instance data, stored in this.

The problem is the contradiction between the last two. I would also say that inheritance is of limited value in JS anyway. It's better to treat it as a functional language:

// higher-order function, returns another function with counter state
var makeCounter = function() {
  var c = 0;
  return function() { return ++c; };
};

// make an object with an 'increment' method:
var incrementable = {
  increment: makeCounter()
};

Personally I tend to avoid constructor functions and prototype inheritance most of the time. They are far less useful in JS than people from an OO background assume.

Update I'd be wary of the statement you quoted in your update:

Private variables only work really well with singleton objects in JavaScript.

That's not really true. An object is just a 'dictionary' of properties, any of which may be functions. So forget objects and think about functions. You can create multiple instances of a function according to some pattern, by writing a function that returns a function. The makeCounter example about is just a simple example of this. makeCounter is not a "singleton object" and doesn't have to be limited to use in singleton objects.

Headgear answered 1/9, 2010 at 11:17 Comment(1)
+1. Good explanation. I'll leave my answer as it is and not try rewriting what you've written here. Personally, I do find prototypes and constructors useful, if only for sharing methods between multiple related objects.Pryor
P
5

The point of the prototype is that it is shared between multiple objects (i.e. those that are created by the same constructor function). If you need variables that are not shared between objects that share a prototype, you need to keep those variables within the constructor function for each object. Just use the prototype for sharing methods.

In your example, you can't do exactly what you want using prototypes. Here's what you could do instead. See Daniel Earwicker's answer for more explanation that there's no point me now replicating here.

var A = function() {};

A.prototype.incrementPublic = function()
{
    return ++this.publicProperty;
};

var B = function()
{
    this.publicProperty = 0;
    var internal = 0;
    this.incrementInternal = function()
    {
      return ++internal;
    };
};

B.prototype = new A();

var x = new B(), y = new B();
console.log(x.incrementPublic(), y.incrementPublic()); // 1, 1
console.log(x.incrementInternal(), y.incrementInternal()); // 1, 1
Pryor answered 1/9, 2010 at 11:1 Comment(5)
But the whole point here is that the functionality belongs in A, and is shared between multiple sub-"classes" (B, C and D). Is there no possibility for non-static private variables in superclasses in Javascript?Monjan
A common way to signify 'private' intentions (aka. warn programmers something special is going on with a property) is to use the underscore as prefix to the property name (ie. obj._privateMember). Though this will work with inheritance for strings and numbers, it starts becomming more complex when using Objects and Arrays as properties.Warfourd
I'm not clear exactly what you're asking. Do you want a variable defined within the function A to be available to methods of B, C and D but nowhere else? Try not to think of things in JavaScript in terms of features of other languages (such as Java or C#). JavaScript simply does not have classes. It has objects which inherit properties from other objects via a prototype chain.Pryor
@Tim: Forgive my class-based thinking, but the variable in A should contain separate values, but be unreachable, for subclasses B, C, and D. My assumption was that the prototype was per object instance/clone, not one instance shared between all inherited objects.Monjan
Vegard: a single prototype object is shared between all objects created by a particular constructor, which is what explains your getting 1 and 2 for x.Increment() and y.Increment().Pryor
R
0

I just found other tricky solution, without exporting any methods/variables to public objects.

function A(inherit) {
    var privates = { //setup private vars, they could be also changed, added in method or children
        a: 1,
        b: 2,
        c: 3
    };
    //setup public methods which uses privates
    this.aPlus = bindPlus("aPlus", this, privates); //pass method name as string!
    this.aGet = bindPlus("aGet", this, privates);
    if (inherit) {
        return privates;
    }
}
A.prototype.aPlus = function () {
    var args = getArgs(arguments),
        //self is "this" here 
        self = args.shift(),
        privates = args.shift(),
        //function real arguments
        n = args.shift();
    return privates.a += n;
};

A.prototype.aGet = function (n) {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.log(this, self, privates);
    return privates.a;
};

//utilites
function getArgs(arg) {
    return Array.prototype.slice.call(arg);
}

function bindPlus(funct, self, privates) {
    return function () {
        return Object.getPrototypeOf(self)[funct].bind(this, self, privates).apply(null, arguments);
    };
}

//inherited 
function B(inherit) {
    var privates = Object.getPrototypeOf(this).constructor.call(this, true);
    privates.d = 4;
    this.dGet = bindPlus("dGet", this, privates);
    if (inherit) {
        return privates;
    }
}

B.prototype = Object.create(A.prototype);
B.constructor = B;

B.prototype.aGet = function () {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.warn("B.aGet", this, privates);
    return privates.a;
};

B.prototype.dGet = function () {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.warn("B.dGet", this, privates);
    return privates.d;
};


// tests
var b = new B();
var a = new A();

//should be 223
console.log("223 ?",b.aPlus(222));

//should be 42
console.log("41",a.aPlus(222));

More test and samples here: http://jsfiddle.net/oceog/TJH9Q/

Reductive answered 21/7, 2014 at 1:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.