Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Private variables in inherited prototypes

Tags:

javascript

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); };
like image 442
Vegard Larsen Avatar asked Sep 01 '10 10:09

Vegard Larsen


People also ask

How prototypal inheritance works?

The Prototypal Inheritance is a feature in javascript used to add methods and properties in objects. It is a method by which an object can inherit the properties and methods of another object. Traditionally, in order to get and set the [[Prototype]] of an object, we use Object. getPrototypeOf and Object.

How is inheritance implemented in JavaScript?

When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.

Which of the following is a feature of prototypical inheritance?

Prototypical inheritance allows us to reuse the properties or methods from one JavaScript object to another through a reference pointer function. All JavaScript objects inherit properties and methods from a prototype: Date objects inherit from Date.

What is the difference between classical and prototypical inheritance?

Classical inheritance is limited to classes inheriting from other classes. However prototypal inheritance includes not only prototypes inheriting from other prototypes but also objects inheriting from prototypes.


3 Answers

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.

like image 132
BGerrissen Avatar answered Oct 28 '22 09:10

BGerrissen


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.

like image 38
Daniel Earwicker Avatar answered Oct 28 '22 10:10

Daniel Earwicker


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
like image 30
Tim Down Avatar answered Oct 28 '22 10:10

Tim Down