继承原型中的私有变量

时间:2010-09-01 10:35:23

标签: javascript

我想我误解了Javascript原型继承是如何工作的。具体来说,原型内部变量似乎在多个不同的子对象之间共享。用代码说明最简单:

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

这会输出1 - 2(在JSBin上测试),而我完全期望结果为1 - 1,因为我想要两个单独的对象。

如何确保A对象不是B的多个实例之间的共享对象?

更新This article突出显示了一些问题:

  

问题在于每个方法用于创建私有变量的范围,其工作正常,也是闭包,在行动中,如果您更改一个对象实例的私有变量,它将被更改为所有。即它更像是私有静态属性,而不是实际的私有变量。

     

所以,如果你想拥有私有的东西,更像是非公共常量,上述任何一种方法都是好的,但不适用于实际的私有变量。私有变量只适用于JavaScript中的单例对象。

解决方案:根据BGerrissen的回答,更改B的声明并保留原型按预期工作:

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

4 个答案:

答案 0 :(得分:17)

私人成员使用原型继承很棘手。首先,它们不能被继承。您需要在每个单独的构造函数中创建私有成员。您可以通过在子类中应用超级构造函数或创建装饰器来完成此操作。

装饰者示例:

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

这只是超级构造函数调用的一种变体,您也可以通过使用.apply()

调用实际的超级构造函数来实现相同的功能。
var B = function(){
    A.apply(this, arguments);
}

现在通过B.prototype = new A()应用继承,您可以从A调用不必要的构造函数代码。避免这种情况的一种方法是使用Douglas Crockfords beget方法:

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

您使用如下:

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

当然,您可以完全放弃继承并充分利用装饰器,至少需要私人成员。

答案 1 :(得分:16)

你需要忘记课程的想法。在JavaScript中并不存在像'B实例'这样的东西。只有'通过调用构造函数B'获得了一些对象。对象具有属性。有些是其“自有”属性,其他属性则通过搜索原型链来包含。

当您说new A时,您正在创建一个对象。然后将其指定为B的原型,这意味着每次调用new B都会生成一个具有相同直接原型的新对象,因此具有相同的计数器变量。

在Tim Down的回答中,显示了两种选择。他的incrementPublic使用继承,但使计数器变量公开(即放弃封装)。而incrementInternal使计数器变为私有,但通过将代码移动到B(即放弃继承)来成功。

你想要的是三件事的组合:

  • 可继承行为 - 因此必须在A中定义并且除了设置原型
  • 之外不需要B中的代码
  • 私有数据,存储在闭包局部变量中
  • 每个实例数据,存储在this

问题是最后两个之间的矛盾。我还要说继承在JS中的价值有限。最好将其视为一种功能语言:

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

我个人倾向于在大多数时候避免使用构造函数和原型继承。它们在JS中的用处远远少于OO背景的人所认为的。

更新我对您在更新中引用的声明持谨慎态度:

  

私有变量只能真正起作用   以及单身对象   的JavaScript。

这不是真的。对象只是属性的“字典”,其中任何一个都可以是函数。所以忘记对象并考虑函数。您可以通过编写返回函数的函数,根据某种模式创建函数的多个实例。 makeCounter示例只是一个简单的例子。 makeCounter不是“单例对象”,不必限制在单例对象中使用。

答案 2 :(得分:5)

原型的要点是它在多个对象(即由相同构造函数创建的对象)之间共享。如果需要在共享原型的对象之间不共享的变量,则需要将这些变量保留在每个对象的构造函数中。只需使用原型来共享方法。

在您的示例中,您无法使用原型完全执行您想要的操作。这是你可以做的事情。请参阅Daniel Earwicker的答案,以获得更多解释,我现在无法复制这一点。

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

答案 3 :(得分:0)

我刚刚找到了其他棘手的解决方案,没有将任何方法/变量导出到公共对象。

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

此处有更多测试和示例:http://jsfiddle.net/oceog/TJH9Q/