Private variables in inherited prototypes
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. Ie 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); };
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.
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 (ie gives up encapsulation). Whereas incrementInternal
makes the counter private but succeeds by moving the code into B
(ie gives up inheritance).
What you want is a combination of three things:
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.
The point of the prototype is that it is shared between multiple objects (ie 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
链接地址: http://www.djcxy.com/p/60770.html
上一篇: 理解Object.create()和新SomeFunction()之间的区别
下一篇: 继承原型中的私有变量