【问题标题】:Private variables in inherited prototypes继承原型中的私有变量
【发布时间】:2011-04-06 17:43:37
【问题描述】:

我想我误解了 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 强调了一些问题:

问题在于,每种方法用于创建私有变量的作用域,它工作正常,也是闭包,实际上,如果您更改一个对象实例的私有变量,它会被所有对象更改. IE。它更像是一个私有静态属性,而不是一个实际的私有变量。

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

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

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

【问题讨论】:

    标签: javascript


    【解决方案1】:

    私有成员在使用原型继承时很棘手。一方面,它们不能被继承。您需要在每个单独的构造函数中创建私有成员。您可以通过在子类中应用超级构造函数或创建装饰器来做到这一点。

    装饰器示例:

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

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

    【讨论】:

      【解决方案2】:

      你需要忘记类的概念。 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 不是“单例对象”,不必限制在单例对象中使用。

      【讨论】:

      • +1。很好的解释。我会保留我的答案,而不是尝试重写你在这里写的内容。就个人而言,我确实发现原型和构造函数很有用,即使只是用于在多个相关对象之间共享方法。
      【解决方案3】:

      原型的关键在于它在多个对象之间共享(即那些由同一个构造函数创建的对象)。如果您需要在共享原型的对象之间不共享的变量,则需要将这些变量保留在每个对象的构造函数中。只需将原型用于共享方法即可。

      在您的示例中,您无法使用原型完全按照您的意愿行事。这是你可以做的。请参阅 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
      

      【讨论】:

      • 但这里的重点是该功能属于A,并在多个子“类”(B、C 和D)之间共享。 Javascript中的超类中是否没有非静态私有变量的可能性?
      • 表示“私有”意图的常用方法(也就是警告程序员某个属性正在发生一些特殊情况)是使用下划线作为属性名称的前缀(即 obj._privateMember)。虽然这适用于字符串和数字的继承,但在使用对象和数组作为属性时它开始变得更加复杂。
      • 我不清楚你在问什么。您是否希望函数A 中定义的变量可用于B、C 和D 的方法,但不能用于其他地方?尽量不要从其他语言(如 Java 或 C#)的特性来考虑 JavaScript 中的事物。 JavaScript 根本没有类。它的对象通过原型链从其他对象继承属性。
      • @Tim:请原谅我基于类的想法,但A 中的变量应该包含单独的值,但对于子类B、C 和D 是不可访问的。我的假设是原型是每个对象实例/克隆,而不是在所有继承对象之间共享一个实例。
      • Vegard:单个原型对象在由特定构造函数创建的所有对象之间共享,这就是为什么您会为 x.Increment() 和 y.Increment() 获得 1 和 2。
      【解决方案4】:

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

      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/

      【讨论】:

        猜你喜欢
        • 2014-11-17
        • 2018-09-19
        • 2014-04-06
        • 2015-03-19
        • 2015-09-14
        • 2016-07-19
        • 1970-01-01
        • 2012-12-03
        • 2012-10-31
        相关资源
        最近更新 更多