【问题标题】:Javascript private member on prototype原型上的 Javascript 私有成员
【发布时间】:2009-01-27 12:26:17
【问题描述】:

好吧,我试图弄清楚这是否有可能。这是代码:

a=function(text)
{
   var b=text;
   if (!arguments.callee.prototype.get)
      arguments.callee.prototype.get=function()
    {
         return b;
    }
    else
      alert('already created!');
}

var c=new a("test");  // creates prototype instance of getter
var d=new a("ojoj");  // alerts already created
alert(c.get())        // alerts test 
alert(d.get())        // alerts test from context of creating prototype function :(

如您所见,我尝试创建原型 getter。为了什么?好吧,如果你写这样的东西:

a=function(text)
{
    var b=text;
    this.getText=function(){ return b}
}

... 一切都应该没问题.. 但实际上每次我创建对象时 - 我都会创建使用内存的 getText 函数。我想在内存中有一个原型函数,它会做同样的事情......有什么想法吗?

编辑:

我尝试了 Christoph 给出的解决方案,它似乎是目前唯一已知的解决方案。它需要记住 id 信息才能从上下文中检索值,但整个想法对我来说很好:) id 只是要记住的一件事,其他所有内容都可以在内存中存储一​​次。事实上,你可以通过这种方式存储很多私有成员,并且任何时候都只使用一个 id。实际上这让我很满意:)(除非有人有更好的主意)。

someFunc = function()
{
  var store = new Array();
  var guid=0;
  var someFunc = function(text)
  {
    this.__guid=guid;
    store[guid++]=text;
  }

  someFunc.prototype.getValue=function()
  {
    return store[this.__guid];
  }

  return someFunc;
}()

a=new someFunc("test");
b=new someFunc("test2");

alert(a.getValue());
alert(b.getValue());

【问题讨论】:

标签: javascript prototype private


【解决方案1】:

JavaScript 传统上不提供属性隐藏机制(“私有成员”)。

由于 JavaScript 是词法范围的,您始终可以通过使用构造函数作为“私有成员”的闭包并在构造函数中定义您的方法,在每个对象级别上模拟它,但这不适用于方法在构造函数的原型属性中定义。

当然,有办法解决这个问题,但我不建议这样做:

Foo = (function() {
    var store = {}, guid = 0;

    function Foo() {
        this.__guid = ++guid;
        store[guid] = { bar : 'baz' };
    }

    Foo.prototype.getBar = function() {
        var privates = store[this.__guid];
        return privates.bar;
    };

    Foo.prototype.destroy = function() {
        delete store[this.__guid];
    };

    return Foo;
})();

这会将“私有”属性存储在与您的Foo 实例分开的另一个对象中。确保在完成对象后调用destroy():否则,您刚刚创建了内存泄漏。


edit 2015-12-01: ECMAScript6 使不需要手动对象破坏的改进变体成为可能,例如通过使用WeakMap 或最好使用Symbol,从而避免需要外部完全存储:

var Foo = (function() {
    var bar = Symbol('bar');

    function Foo() {
        this[bar] = 'baz';
    }

    Foo.prototype.getBar = function() {
        return this[bar];
    };

    return Foo;
})();

【讨论】:

  • 这不是说我每次调用 new Foo () 时都会创建一个全新的函数对象吗??
  • 这里看起来不太对劲...当 Foo 类有 2 个实例时如何处理?
  • 哦,我明白了……“商店”是全班共享的,但商店中的条目是每个实例的。
  • @4esn0k 如本文所述fitzgeraldnick.com/weblog/53,在这里使用weakMap 会更合适,因为您可以通过Object.getOwnPropertySymbols 访问Symbols
【解决方案2】:

随着现代浏览器采用一些 ES6 技术,您可以使用WeakMap 来解决 GUID 问题。这适用于 IE11 及更高版本:

// Scope private vars inside an IIFE
var Foo = (function() { 
    // Store all the Foos, and garbage-collect them automatically
    var fooMap = new WeakMap();

    var Foo = function(txt) { 
        var privateMethod = function() { 
            console.log(txt); 
        };
        // Store this Foo in the WeakMap
        fooMap.set(this, {privateMethod: privateMethod}); 
    } 

    Foo.prototype = Object.create(Object.prototype); 
    Foo.prototype.public = function() { 
        fooMap.get(this).p(); 
     } 
     return Foo; 
 }());

 var foo1 = new Foo("This is foo1's private method");
 var foo2 = new Foo("This is foo2's private method");
 foo1.public(); // "This is foo1's private method"
 foo2.public(); // "This is foo2's private method"

WeakMap 在被删除或超出范围后不会存储对任何Foo 的引用,并且由于它使用对象作为键,因此您不需要将 GUID 附加到您的对象。

【讨论】:

  • +1,但如果你使用 ES6,为什么不使用 symbols
  • @Christoph:因为符号附加到对象上,您可以使用Object.getOwnPropertySymbols() 获取它们。您可能已经发现了这一点...
【解决方案3】:

原型上的方法不能访问“私有”成员,因为它们存在于 javascript 中;你需要某种特权访问者。由于您声明了get,它可以在词法上看到b,因此它将始终返回b 在创建时的内容。

【讨论】:

    【解决方案4】:

    在受到 Christoph 的变通方法的极大启发后,我想出了一个稍微修改的概念,它提供了一些增强功能。同样,这个解决方案很有趣,但不一定推荐。这些增强功能包括:

    • 不再需要在构造函数中执行任何设置
    • 不再需要在实例上存储公共 GUID
    • 添加了一些语法糖

    本质上,这里的技巧是使用实例对象本身作为访问相关私有对象的键。通常这对于普通对象是不可能的,因为它们的键必须是字符串。但是,我能够使用表达式({} === {}) 返回false 的事实来完成此操作。换句话说,比较运算符可以辨别唯一的对象实例。

    长话短说,我们可以使用两个数组来维护实例及其关联的私有对象:

    Foo = (function() {
        var instances = [], privates = [];
    
        // private object accessor function
        function _(instance) {
            var index = instances.indexOf(instance), privateObj;
    
            if(index == -1) {
                // Lazily associate instance with a new private object
                instances.push(instance);
                privates.push(privateObj = {});
            }
            else {
                // A privateObject has already been created, so grab that
                privateObj = privates[index];
            }
            return privateObj;
        }
    
        function Foo() {
            _(this).bar = "This is a private bar!";
        }
    
        Foo.prototype.getBar = function() {
            return _(this).bar;
        };
    
        return Foo;
    })();
    

    您会注意到上面的_ 函数。这是获取私有对象的访问器函数。它工作得很慢,所以如果你用一个新实例调用它,它会动态创建一个新的私有对象。

    如果您不想为每个类复制 _ 代码,可以通过将其包装在工厂函数中来解决此问题:

    function createPrivateStore() {
        var instances = [], privates = [];
    
        return function (instance) {
            // Same implementation as example above ...
        };
    }
    

    现在您可以将每个类减少到一行:

    var _ = createPrivateStore();
    

    同样,您必须非常小心使用此解决方案,因为如果您不实施并在必要时调用销毁函数,它可能造成内存泄漏。

    【讨论】:

      【解决方案5】:

      就我个人而言,我不太喜欢使用 guid 的解决方案,因为它迫使开发人员在 store 之外声明它并在构造函数中增加它。在大型 javascript 应用程序中,开发人员可能会忘记这样做,这很容易出错。

      我非常喜欢 Peter 的回答,因为您可以使用上下文 (this) 访问私有成员。但是让我非常困扰的一件事是,对私有成员的访问是在 o(n) 复杂度中完成的。实际上,在数组中查找对象的索引是一种线性算法。假设您想将此模式用于实例化 10000 次的对象。然后,您可能会在每次想要访问私有成员时迭代 10000 个实例。

      为了以 o(1) 复杂度访问私有存储,除了使用 guid 之外别无他法。但为了不打扰 guid 声明和增量,并为了使用上下文访问私有存储,我修改了 Peters 工厂模式如下:

      createPrivateStore = function () {
      var privates = {}, guid = 0;
      
      return function (instance) {
          if (instance.__ajxguid__ === undefined) {
              // Lazily associate instance with a new private object
              var private_obj = {};
              instance.__ajxguid__ = ++guid;
              privates[instance.__ajxguid__] = private_obj;
              return private_obj;
          }
      
          return privates[instance.__ajxguid__];
      }
      

      }

      这里的诀窍是考虑到尚未处理没有 ajxguid 属性的对象。确实,可以在第一次访问商店之前手动设置属性,但我认为没有神奇的解决方案。

      【讨论】:

        【解决方案6】:

        我认为真正的隐私被高估了。只需要虚拟隐私即可。我认为使用 _privateIdentifier 是朝着正确方向迈出的一步,但还不够远,因为您仍然会在智能感知弹出窗口中看到所有 _privateIdentifiers 的列表。更进一步和更好的步骤是在原型和/或构造函数中创建一个对象,用于将您的虚拟私有字段和方法隔离在视线之外,如下所示:

          // Create the object
          function MyObject() {}
        
          // Add methods to the prototype
          MyObject.prototype = {
        
            // This is our public method
            public: function () {
              console.log('PUBLIC method has been called');
            },
        
            // This is our private method tucked away inside a nested privacy object called x
            x: {
              private: function () {
                console.log('PRIVATE method has been called');
              }
            },
        
          }
        
        // Create an instance of the object
        var mo = new MyObject(); 
        

        现在当编码器键入“mo”时。智能感知只会显示公共函数和“x”。因此,所有私有成员都没有显示,而是隐藏在“x”后面,这使得编码人员不太可能意外调用私有成员,因为他们必须特意输入“mo.x”。查看私人会员。此方法还避免了智能感知列表中的多个私有成员名称混乱,将它们全部隐藏在单个项目“x”后面。

        【讨论】:

          【解决方案7】:

          我知道这个帖子现在真的很老了,但我认为这个解决方案可能会让路过的人感兴趣:

          const Immutable = function ( val ) {
              let _val = val;
          
              this.$ = {
                  _resolve: function () {
                      return _val;
                  }
              };
          };
          Immutable.prototype = {
              resolve: function () {
                  return this.$._resolve();
              }
          };
          

          本质上隐藏了内部 _val 使其不被操纵并使该对象的实例不可变。

          【讨论】:

          • TBH,我越看这个,我就越意识到它有点违背了使用 .prototype 的对象。但我现在想不出更好的办法。
          【解决方案8】:

          我创建了一个新库,用于在原型链上启用私有方法。 https://github.com/TremayneChrist/ProtectJS

          例子:

          var MyObject = (function () {
          
            // Create the object
            function MyObject() {}
          
            // Add methods to the prototype
            MyObject.prototype = {
          
              // This is our public method
              public: function () {
                console.log('PUBLIC method has been called');
              },
          
              // This is our private method, using (_)
              _private: function () {
                console.log('PRIVATE method has been called');
              }
            }
          
            return protect(MyObject);
          
          })();
          
          // Create an instance of the object
          var mo = new MyObject();
          
          // Call its methods
          mo.public(); // Pass
          mo._private(); // Fail
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-09-28
            • 1970-01-01
            • 1970-01-01
            • 2010-09-30
            • 2013-01-03
            • 2015-03-19
            • 2020-09-18
            相关资源
            最近更新 更多