【问题标题】:Accessing private member variables from prototype-defined functions从原型定义的函数访问私有成员变量
【发布时间】:2010-09-30 23:17:00
【问题描述】:

有什么方法可以使“私有”变量(在构造函数中定义的)可用于原型定义的方法?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

这行得通:

t.nonProtoHello()

但这不是:

t.prototypeHello()

我习惯于在构造函数中定义我的方法,但出于几个原因,我不再这样做了。

【问题讨论】:

标签: javascript private-members


【解决方案1】:

ES6 弱映射

通过使用基于 ES6 WeakMaps 的简单模式,可以获得可从原型函数访问的私有成员变量

注意:WeakMaps 的使用通过让垃圾收集器识别并丢弃未使用的实例来保证防止内存泄漏

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

这个模式更详细的解释可以看here

【讨论】:

    【解决方案2】:

    我知道自从被问到这个问题已经超过 10 年了,但我只是在我的程序员生命中第 n 次思考这个问题,并找到了一个可能的解决方案,我不知道我是否完全喜欢呢。我以前没有见过这种方法的记录,所以我将其命名为“私人/公共美元模式”或 _$ / $ 模式

    var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
    var ownFieldValue = this._$("fieldName"[, newValue]);
    
    var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);
    
    //Throws an exception. objectX._$ is not defined
    var objectFieldValue = objectX._$("fieldName"[, newValue]);
    

    这个概念使用一个 ClassDefinition 函数,该函数返回一个 Constructor 函数,该函数返回一个 Interface 对象。该接口的唯一方法是$,它接收name 参数以调用构造函数对象中的相应函数,在name 之后传递的任何附加参数都会在调用中传递。

    全局定义的辅助函数ClassValues 根据需要将所有字段存储在an 对象中。它定义了_$ 函数以通过name 访问它们。这遵循一个简短的 get/set 模式,因此如果 value 被传递,它将被用作新的变量值。

    var ClassValues = function (values) {
      return {
        _$: function _$(name, value) {
          if (arguments.length > 1) {
            values[name] = value;
          }
    
          return values[name];
        }
      };
    };
    

    全局定义的函数Interface 接受一个对象和一个Values 对象以返回一个_interface 和一个函数$,它检查obj 以找到一个以参数name 命名的函数并调用它以values 作为 scoped 对象。传递给$ 的附加参数将在函数调用时传递。

    var Interface = function (obj, values, className) {
      var _interface = {
        $: function $(name) {
          if (typeof(obj[name]) === "function") {
            return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
          }
    
          throw className + "." + name + " is not a function.";
        }
      };
    
      //Give values access to the interface.
      values.$ = _interface.$;
    
      return _interface;
    };
    

    在下面的示例中,ClassX 分配给ClassDefinition 的结果,即Constructor 函数。 Constructor 可以接收任意数量的参数。 Interface 是外部代码调用构造函数后得到的。

    var ClassX = (function ClassDefinition () {
      var Constructor = function Constructor (valA) {
        return Interface(this, ClassValues({ valA: valA }), "ClassX");
      };
    
      Constructor.prototype.getValA = function getValA() {
        //private value access pattern to get current value.
        return this._$("valA");
      };
    
      Constructor.prototype.setValA = function setValA(valA) {
        //private value access pattern to set new value.
        this._$("valA", valA);
      };
    
      Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
        //interface access pattern to call object function.
        var valA = this.$("getValA");
    
        //timesAccessed was not defined in constructor but can be added later...
        var timesAccessed = this._$("timesAccessed");
    
        if (timesAccessed) {
          timesAccessed = timesAccessed + 1;
        } else {
          timesAccessed = 1;
        }
    
        this._$("timesAccessed", timesAccessed);
    
        if (valA) {
          return "valA is " + validMessage + ".";
        }
    
        return "valA is " + invalidMessage + ".";
      };
    
      return Constructor;
    }());
    

    Constructor 中使用非原型函数是没有意义的,尽管您可以在构造函数体中定义它们。所有函数都以 公共美元模式 this.$("functionName"[, param1[, param2 ...]]) 调用。私有值通过 私有美元模式 this._$("valueName"[, replacingValue]); 访问。由于Interface 没有_$ 的定义,因此外部对象无法访问这些值。由于每个原型函数体的this 都设置为函数$ 中的values 对象,因此如果直接调用Constructor 兄弟函数,则会出现异常; _$ / $ 模式 也需要在原型函数体中遵循。以下示例用法。

    var classX1 = new ClassX();
    console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
    console.log("classX1.valA: " + classX1.$("getValA"));
    classX1.$("setValA", "v1");
    console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
    var classX2 = new ClassX("v2");
    console.log("classX1.valA: " + classX1.$("getValA"));
    console.log("classX2.valA: " + classX2.$("getValA"));
    //This will throw an exception
    //classX1._$("valA");
    

    还有控制台输出。

    classX1.valA is invalid.
    classX1.valA: undefined
    classX1.valA is valid.
    classX1.valA: v1
    classX2.valA: v2
    

    _$ / $ 模式 允许在完全原型化的类中完全保密值。我不知道我是否会使用它,也不知道它是否有缺陷,但是嘿,这是一个很好的拼图!

    【讨论】:

      【解决方案3】:

      当我读到这篇文章时,这听起来像是一个艰巨的挑战,所以我决定想办法。我想出的是CRAAAAZY,但它完全有效。

      首先,我尝试在即时函数中定义类,以便您可以访问该函数的一些私有属性。这很有效,并且允许您获取一些私有数据,但是,如果您尝试设置私有数据,您很快就会发现所有对象都将共享相同的值。

      var SharedPrivateClass = (function() { // use immediate function
          // our private data
          var private = "Default";
      
          // create the constructor
          function SharedPrivateClass() {}
      
          // add to the prototype
          SharedPrivateClass.prototype.getPrivate = function() {
              // It has access to private vars from the immediate function!
              return private;
          };
      
          SharedPrivateClass.prototype.setPrivate = function(value) {
              private = value;
          };
      
          return SharedPrivateClass;
      })();
      
      var a = new SharedPrivateClass();
      console.log("a:", a.getPrivate()); // "a: Default"
      
      var b = new SharedPrivateClass();
      console.log("b:", b.getPrivate()); // "b: Default"
      
      a.setPrivate("foo"); // a Sets private to "foo"
      console.log("a:", a.getPrivate()); // "a: foo"
      console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!
      
      console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
      console.log(a.private); // undefined
      
      // getPrivate() is only created once and instanceof still works
      console.log(a.getPrivate === b.getPrivate);
      console.log(a instanceof SharedPrivateClass);
      console.log(b instanceof SharedPrivateClass);

      在很多情况下,如果您想要在实例之间共享事件名称等常量值,这已经足够了。但本质上,它们就像私有静态变量一样。

      如果您绝对需要从原型上定义的方法中访问私有命名空间中的变量,您可以尝试这种模式。

      var PrivateNamespaceClass = (function() { // immediate function
          var instance = 0, // counts the number of instances
              defaultName = "Default Name",  
              p = []; // an array of private objects
      
          // create the constructor
          function PrivateNamespaceClass() {
              // Increment the instance count and save it to the instance. 
              // This will become your key to your private space.
              this.i = instance++; 
              
              // Create a new object in the private space.
              p[this.i] = {};
              // Define properties or methods in the private space.
              p[this.i].name = defaultName;
              
              console.log("New instance " + this.i);        
          }
      
          PrivateNamespaceClass.prototype.getPrivateName = function() {
              // It has access to the private space and it's children!
              return p[this.i].name;
          };
          PrivateNamespaceClass.prototype.setPrivateName = function(value) {
              // Because you use the instance number assigned to the object (this.i)
              // as a key, the values set will not change in other instances.
              p[this.i].name = value;
              return "Set " + p[this.i].name;
          };
      
          return PrivateNamespaceClass;
      })();
      
      var a = new PrivateNamespaceClass();
      console.log(a.getPrivateName()); // Default Name
      
      var b = new PrivateNamespaceClass();
      console.log(b.getPrivateName()); // Default Name
      
      console.log(a.setPrivateName("A"));
      console.log(b.setPrivateName("B"));
      console.log(a.getPrivateName()); // A
      console.log(b.getPrivateName()); // B
      
      // private objects are not accessible outside the PrivateNamespaceClass function
      console.log(a.p);
      
      // the prototype functions are not re-created for each instance
      // and instanceof still works
      console.log(a.getPrivateName === b.getPrivateName);
      console.log(a instanceof PrivateNamespaceClass);
      console.log(b instanceof PrivateNamespaceClass);

      如果有人发现这种方式存在错误,我希望得到一些反馈。

      【讨论】:

      • 我想一个潜在的问题是任何实例都可以通过使用不同的实例 ID 访问任何其他实例私有变量。不一定是坏事...
      • 你在每次构造函数调用时重新定义原型函数
      • @Lu4 我不确定这是不是真的。构造函数是从闭包中返回的;唯一一次定义原型函数是第一次,即立即调用的函数表达式。撇开上面提到的隐私问题不谈,这对我来说看起来不错(乍一看)。
      • @MimsH.Wright 其他语言允许访问 同一类 的其他私有对象,但前提是您必须引用它们。为此,您可以将私有信息隐藏在将对象指针作为键(与 ID 相对)的函数后面。这样你就只能访问你知道的对象的私有数据,这更符合其他语言的范围。然而,这个实现揭示了一个更深层次的问题。在构造函数被回收之前,私有对象永远不会被垃圾收集。
      • 我想提一下 i 已添加到所有实例中。所以它不是完全“透明”的,i 仍然可以被篡改。
      【解决方案4】:

      您需要在代码中更改 3 件事:

      1. var privateField = "hello" 替换为this.privateField = "hello"
      2. 在原型中将privateField 替换为this.privateField
      3. 在非原型中也将privateField 替换为this.privateField

      最终代码如下:

      TestClass = function(){
          this.privateField = "hello";
          this.nonProtoHello = function(){alert(this.privateField)};
      }
      
      TestClass.prototype.prototypeHello = function(){alert(this.privateField)};
      
      var t = new TestClass();
      
      t.prototypeHello()
      

      【讨论】:

      • this.privateField 不会是私有字段。可从外部访问:t.privateField
      【解决方案5】:

      今天正在玩这个,这是我在不使用符号的情况下能找到的唯一解决方案。最好的一点是它实际上可以完全保密。

      该解决方案基于一个本地模块加载器,它基本上成为私有存储缓存的中介(使用弱映射)。

         const loader = (function() {
              function ModuleLoader() {}
      
          //Static, accessible only if truly needed through obj.constructor.modules
          //Can also be made completely private by removing the ModuleLoader prefix.
          ModuleLoader.modulesLoaded = 0;
          ModuleLoader.modules = {}
      
          ModuleLoader.prototype.define = function(moduleName, dModule) {
              if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');
      
              const module = ModuleLoader.modules[moduleName] = {}
      
              module.context = {
                  __moduleName: moduleName,
                  exports: {}
              }
      
              //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
              module._private = {
                  private_sections: new WeakMap(),
                  instances: []
              };
      
              function private(action, instance) {
                  switch (action) {
                      case "create":
                          if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                          module._private.instances.push(instance);
                          module._private.private_sections.set(instance, {});
                          break;
                      case "delete":
                          const index = module._private.instances.indexOf(instance);
                          if (index == -1) throw new Error('Invalid state');
                          module._private.instances.slice(index, 1);
                          return module._private.private_sections.delete(instance);
                          break;
                      case "get":
                          return module._private.private_sections.get(instance);
                          break;
                      default:
                          throw new Error('Invalid action');
                          break;
                  }
              }
      
              dModule.call(module.context, private);
              ModuleLoader.modulesLoaded++;
          }
      
          ModuleLoader.prototype.remove = function(moduleName) {
              if (!moduleName in (ModuleLoader.modules)) return;
      
              /*
                  Clean up as best we can.
              */
              const module = ModuleLoader.modules[moduleName];
              module.context.__moduleName = null;
              module.context.exports = null;
              module.cotext = null;
              module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
              for (let i = 0; i < module._private.instances.length; i++) {
                  module._private.instances[i] = undefined;
              }
              module._private.instances = undefined;
              module._private = null;
              delete ModuleLoader.modules[moduleName];
              ModuleLoader.modulesLoaded -= 1;
          }
      
      
          ModuleLoader.prototype.require = function(moduleName) {
              if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');
      
              return ModuleLoader.modules[moduleName].context.exports;
          }
      
      
      
           return new ModuleLoader();
          })();
      
          loader.define('MyModule', function(private_store) {
              function MyClass() {
                  //Creates the private storage facility. Called once in constructor.
                  private_store("create", this);
      
      
                  //Retrieve the private storage object from the storage facility.
                  private_store("get", this).no = 1;
              }
      
              MyClass.prototype.incrementPrivateVar = function() {
                  private_store("get", this).no += 1;
              }
      
              MyClass.prototype.getPrivateVar = function() {
                  return private_store("get", this).no;
              }
      
              this.exports = MyClass;
          })
      
          //Get whatever is exported from MyModule
          const MyClass = loader.require('MyModule');
      
          //Create a new instance of `MyClass`
          const myClass = new MyClass();
      
          //Create another instance of `MyClass`
          const myClass2 = new MyClass();
      
          //print out current private vars
          console.log('pVar = ' + myClass.getPrivateVar())
          console.log('pVar2 = ' + myClass2.getPrivateVar())
      
          //Increment it
          myClass.incrementPrivateVar()
      
          //Print out to see if one affected the other or shared
          console.log('pVar after increment = ' + myClass.getPrivateVar())
          console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())
      
          //Clean up.
          loader.remove('MyModule')
      

      【讨论】:

        【解决方案6】:

        我有一个解决方案,但我不确定它是否没有缺陷。

        要使其工作,您必须使用以下结构:

        1. 使用 1 个包含所有私有变量的私有对象。
        2. 使用 1 个实例函数。
        3. 对构造函数和所有原型函数应用闭包。
        4. 创建的任何实例都是在定义的闭包之外完成的。

        代码如下:

        var TestClass = 
        (function () {
            // difficult to be guessed.
            var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
            var TestClass = function () {
                var privateFields = {
                    field1: 1,
                    field2: 2
                };
                this.getPrivateFields = function (hashed) {
                    if(hashed !== hash) {
                        throw "Cannot access private fields outside of object.";
                        // or return null;
                    }
                    return privateFields;
                };
            };
        
            TestClass.prototype.prototypeHello = function () {
                var privateFields = this.getPrivateFields(hash);
                privateFields.field1 = Math.round(Math.random() * 100);
                privateFields.field2 = Math.round(Math.random() * 100);
            };
        
            TestClass.prototype.logField1 = function () {
                var privateFields = this.getPrivateFields(hash);
                console.log(privateFields.field1);
            };
        
            TestClass.prototype.logField2 = function () {
                var privateFields = this.getPrivateFields(hash);
                console.log(privateFields.field2);
            };
        
            return TestClass;
        })();
        

        它的工作原理是它提供了一个实例函数“this.getPrivateFields”来访问“privateFields”私有变量对象,但是这个函数只会返回定义的主闭包内的“privateFields”对象(也使用“原型函数” this.getPrivateFields" 需要在这个闭包中定义)。

        在运行时产生且难以猜测的哈希用作参数,以确保即使在闭包范围之外调用“getPrivateFields”也不会返回“privateFields”对象。

        缺点是我们不能用闭包之外的更多原型函数来扩展TestClass。

        这是一些测试代码:

        var t1 = new TestClass();
        console.log('Initial t1 field1 is: ');
        t1.logField1();
        console.log('Initial t1 field2 is: ');
        t1.logField2();
        t1.prototypeHello();
        console.log('t1 field1 is now: ');
        t1.logField1();
        console.log('t1 field2 is now: ');
        t1.logField2();
        var t2 = new TestClass();
        console.log('Initial t2 field1 is: ');
        t2.logField1();
        console.log('Initial t2 field2 is: ');
        t2.logField2();
        t2.prototypeHello();
        console.log('t2 field1 is now: ');
        t2.logField1();
        console.log('t2 field2 is now: ');
        t2.logField2();
        
        console.log('t1 field1 stays: ');
        t1.logField1();
        console.log('t1 field2 stays: ');
        t1.logField2();
        
        t1.getPrivateFields(11233);
        

        编辑:使用这种方法,也可以“定义”私有函数。

        TestClass.prototype.privateFunction = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private function.";
            }
        };
        
        TestClass.prototype.prototypeHello = function () {
            this.privateFunction(hash);
        };
        

        【讨论】:

          【解决方案7】:
          var getParams = function(_func) {
            res = _func.toString().split('function (')[1].split(')')[0].split(',')
            return res
          }
          
          function TestClass(){
          
            var private = {hidden: 'secret'}
            //clever magic accessor thing goes here
            if ( !(this instanceof arguments.callee) ) {
              for (var key in arguments) {
                if (typeof arguments[key] == 'function') {
                  var keys = getParams(arguments[key])
                  var params = []
                  for (var i = 0; i <= keys.length; i++) {
                    if (private[keys[i]] != undefined) {
                      params.push(private[keys[i]])
                    }
                  }
                  arguments[key].apply(null,params)
                }
              }
            }
          }
          
          
          TestClass.prototype.test = function(){
            var _hidden; //variable I want to get
            TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
          };
          
          new TestClass().test()
          

          这是怎么回事?使用私有访问器。仅允许您获取变量而不设置它们,具体取决于用例。

          【讨论】:

          • 这确实以有用的方式回答了这个问题。 为什么你相信这就是答案? 如何它是如何工作的?在没有任何上下文或含义的情况下简单地告诉某人更改他们的代码并不能帮助他们了解他们做错了什么。
          • 他想要一种方法来通过原型访问类的隐藏私有变量,而不必在类的每个实例上创建该隐藏变量。上面的代码是这样做的示例方法。这怎么不是问题的答案?
          • 我没有说这不是问题的答案。我说这不是一个有用的答案,因为它无助于任何人学习。您应该解释您的代码,为什么它有效,为什么它是正确的方法。如果我是问题的作者,我不会接受你的回答,因为它不鼓励学习,不告诉我我做错了什么或给定的代码在做什么或它是如何工作的。
          【解决方案8】:

          我迟到了,但我想我可以做出贡献。在这里,检查一下:

          // 1. Create closure
          var SomeClass = function() {
            // 2. Create `key` inside a closure
            var key = {};
            // Function to create private storage
            var private = function() {
              var obj = {};
              // return Function to access private storage using `key`
              return function(testkey) {
                if(key === testkey) return obj;
                // If `key` is wrong, then storage cannot be accessed
                console.error('Cannot access private properties');
                return undefined;
              };
            };
            var SomeClass = function() {
              // 3. Create private storage
              this._ = private();
              // 4. Access private storage using the `key`
              this._(key).priv_prop = 200;
            };
            SomeClass.prototype.test = function() {
              console.log(this._(key).priv_prop); // Using property from prototype
            };
            return SomeClass;
          }();
          
          // Can access private property from within prototype
          var instance = new SomeClass();
          instance.test(); // `200` logged
          
          // Cannot access private property from outside of the closure
          var wrong_key = {};
          instance._(wrong_key); // undefined; error logged

          我将此方法称为访问器模式。基本思想是我们有一个闭包,闭包内部有一个key,并且我们创建了一个私有对象(在构造函数中),它可以仅当您拥有 key 时才能访问。

          如果您有兴趣,可以在my article 阅读更多相关信息。使用此方法,您可以创建在闭包之外无法访问的每个对象的属性。因此,您可以在构造函数或原型中使用它们,但不能在其他任何地方使用它们。我还没有看到这种方法在任何地方使用过,但我认为它真的很强大。

          【讨论】:

            【解决方案9】:

            我今天遇到了完全相同的问题,在详细说明 Scott Rippey 一流的响应之后,我想出了一个非常简单的解决方案(恕我直言),它既兼容 ES5 又高效,而且名称冲突安全(使用 _private似乎不安全)。

            /*jslint white: true, plusplus: true */
            
             /*global console */
            
            var a, TestClass = (function(){
                "use strict";
                function PrefixedCounter (prefix) {
                    var counter = 0;
                    this.count = function () {
                        return prefix + (++counter);
                    };
                }
                var TestClass = (function(){
                    var cls, pc = new PrefixedCounter("_TestClass_priv_")
                    , privateField = pc.count()
                    ;
                    cls = function(){
                        this[privateField] = "hello";
                        this.nonProtoHello = function(){
                            console.log(this[privateField]);
                        };
                    };
                    cls.prototype.prototypeHello = function(){
                        console.log(this[privateField]);
                    };
                    return cls;
                }());
                return TestClass;
            }());
            
            a = new TestClass();
            a.nonProtoHello();
            a.prototypeHello();
            

            用 ringojs 和 nodejs 测试。我渴望阅读您的意见。

            【讨论】:

            【解决方案10】:

            有一个非常简单的方法来做到这一点

            function SharedPrivate(){
              var private = "secret";
              this.constructor.prototype.getP = function(){return private}
              this.constructor.prototype.setP = function(v){ private = v;}
            }
            
            var o1 = new SharedPrivate();
            var o2 = new SharedPrivate();
            
            console.log(o1.getP()); // secret
            console.log(o2.getP()); // secret
            o1.setP("Pentax Full Frame K1 is on sale..!");
            console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
            console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
            o2.setP("And it's only for $1,795._");
            console.log(o1.getP()); // And it's only for $1,795._
            

            JavaScript 原型是黄金。

            【讨论】:

            • 我认为最好不要在构造函数中使用原型,因为它会在每次创建新实例时创建一个新函数。
            • @whamsicore 是的,但在这种情况下,这是必不可少的,因为对于每个实例化的对象,我们都必须安排一个共享闭包。这就是函数定义驻留在构造函数中的原因,我们必须将SharedPrivate.prototype 称为this.constructor.prototype 多次重新定义getP 和setP 没什么大不了的......
            【解决方案11】:

            这是我在尝试为这个问题找到最简单的解决方案时想出的东西,也许它可能对某人有用。我是 javascript 新手,所以代码可能存在一些问题。

            // pseudo-class definition scope
            (function () {
            
                // this is used to identify 'friend' functions defined within this scope,
                // while not being able to forge valid parameter for GetContext() 
                // to gain 'private' access from outside
                var _scope = new (function () { })();
                // -----------------------------------------------------------------
            
                // pseudo-class definition
                this.Something = function (x) {
            
                    // 'private' members are wrapped into context object,
                    // it can be also created with a function
                    var _ctx = Object.seal({
            
                        // actual private members
                        Name: null,
                        Number: null,
            
                        Somefunc: function () {
                            console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
                        }
                    });
                    // -----------------------------------------------------------------
            
                    // function below needs to be defined in every class
                    // to allow limited access from prototype
                    this.GetContext = function (scope) {
            
                        if (scope !== _scope) throw 'access';
                        return _ctx;
                    }
                    // -----------------------------------------------------------------
            
                    {
                        // initialization code, if any
                        _ctx.Name = (x !== 'undefined') ? x : 'default';
                        _ctx.Number = 0;
            
                        Object.freeze(this);
                    }
                }
                // -----------------------------------------------------------------
            
                // prototype is defined only once
                this.Something.prototype = Object.freeze({
            
                    // public accessors for 'private' field
                    get Number() { return this.GetContext(_scope).Number; },
                    set Number(v) { this.GetContext(_scope).Number = v; },
            
                    // public function making use of some private fields
                    Test: function () {
            
                        var _ctx = this.GetContext(_scope);
                        // access 'private' field
                        console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
                        // call 'private' func
                        _ctx.Somefunc();
                    }
                });
                // -----------------------------------------------------------------
            
                // wrap is used to hide _scope value and group definitions
            }).call(this);
            
            function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
            // -----------------------------------------------------------------
            
            function test_smth() {
            
                console.clear();
            
                var smth1 = new Something('first'),
                  smth2 = new Something('second');
            
                //_A(false);
                _A(smth1.Test === smth2.Test);
            
                smth1.Number = 3;
                smth2.Number = 5;
                console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);
            
                smth1.Number = 2;
                smth2.Number = 6;
            
                smth1.Test();
                smth2.Test();
            
                try {
                    var ctx = smth1.GetContext();
                } catch (err) {
                    console.log('error: ' + err);
                }
            }
            
            test_smth();
            

            【讨论】:

              【解决方案12】:

              这是我想出的。

              (function () {
                  var staticVar = 0;
                  var yrObj = function () {
                      var private = {"a":1,"b":2};
                      var MyObj = function () {
                          private.a += staticVar;
                          staticVar++;
                      };
                      MyObj.prototype = {
                          "test" : function () {
                              console.log(private.a);
                          }
                      };
              
                      return new MyObj;
                  };
                  window.YrObj = yrObj;
              }());
              
              var obj1 = new YrObj;
              var obj2 = new YrObj;
              obj1.test(); // 1
              obj2.test(); // 2
              

              这个实现的主要问题是它重新定义了每个实例的原型。

              【讨论】:

              • 有趣,我真的很喜欢这种尝试并且正在考虑同样的事情,但你说得对,在每次实例化时重新定义原型函数是一个很大的限制。这不仅是因为它浪费了 CPU 周期,还因为如果您稍后更改原型,它将在下一次实例化时“重置”回构造函数中定义的原始状态:/
              • 这不仅重新定义了原型,还为每个实例定义了一个新的构造函数。所以“实例”不再是同一类的实例。
              【解决方案13】:

              更新:使用 ES6,有一个更好的方法:

              长话短说,您可以使用新的Symbol 创建私有字段。
              这是一个很好的描述:https://curiosity-driven.org/private-properties-in-javascript

              例子:

              var Person = (function() {
                  // Only Person can access nameSymbol
                  var nameSymbol = Symbol('name');
              
                  function Person(name) {
                      this[nameSymbol] = name;
                  }
              
                  Person.prototype.getName = function() {
                      return this[nameSymbol];
                  };
              
                  return Person;
              }());
              

              对于所有带有 ES5 的现代浏览器:

              你可以只使用闭包

              构造对象的最简单方法是完全避免原型继承。 只需在闭包中定义私有变量和公共函数,所有公共方法都可以私有访问这些变量。

              或者你可以只使用原型

              在 JavaScript 中,原型继承主要是一种优化。它允许多个实例共享原型方法,而不是每个实例都有自己的方法。
              缺点是 this 是每次调用原型函数时唯一不同的东西。
              因此,任何私有字段都必须可以通过this 访问,这意味着它们将是公开的。所以我们只是坚持_private 字段的命名约定。

              不要费心把闭包和原型混在一起

              我认为您不应该将闭包变量与原型方法混合使用。您应该使用其中一种。

              当您使用闭包访问私有变量时,原型方法无法访问该变量。因此,您必须将闭包公开到this,这意味着您以一种或另一种方式公开公开它。这种方法几乎没有什么好处。

              我该选哪个?

              对于非常简单的对象,只需使用带有闭包的普通对象即可。

              如果您需要原型继承(用于继承、性能等),请坚持使用“_private”命名约定,不要为闭包而烦恼。

              我不明白为什么 JS 开发人员如此努力地使字段真正私有。

              【讨论】:

              • 遗憾的是,如果您想利用原型继承,_private 命名约定仍然是最佳解决方案。
              • ES6 将有一个新概念,Symbol,这是创建私有字段的绝佳方式。这是一个很好的解释:curiosity-driven.org/private-properties-in-javascript
              • 不,您可以将Symbol 保存在包含整个班级的闭包中。这样一来,所有原型方法都可以使用 Symbol,但它永远不会暴露在类之外。
              • 您链接的文章说“符号类似于私人名称,但与私人名称不同,它们不提供真正的隐私”。实际上,如果您有实例,您可以使用Object.getOwnPropertySymbols 获取其符号。所以这只是默默无闻的隐私。
              • @Oriol 是的,隐私非常隐蔽。仍然可以遍历符号,并通过toString 推断符号的用途。这与 Java 或 C# 没有什么不同......私有成员仍然可以通过反射访问,但通常被强烈地遮蔽。这一切都加强了我的最后一点,“我不明白为什么 JS 开发人员如此努力地使字段真正私有。”
              【解决方案14】:

              试试吧!

                  function Potatoe(size) {
                  var _image = new Image();
                  _image.src = 'potatoe_'+size+'.png';
                  function getImage() {
                      if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
                          throw new Error('This is a private property.');
                      return _image;
                  }
                  Object.defineProperty(this,'image',{
                      configurable: false,
                      enumerable: false,
                      get : getImage          
                  });
                  Object.defineProperty(this,'size',{
                      writable: false,
                      configurable: false,
                      enumerable: true,
                      value : size            
                  });
              }
              Potatoe.prototype.draw = function(ctx,x,y) {
                  //ctx.drawImage(this.image,x,y);
                  console.log(this.image);
              }
              Potatoe.prototype.draw.owner = Potatoe.prototype;
              
              var pot = new Potatoe(32);
              console.log('Potatoe size: '+pot.size);
              try {
                  console.log('Potatoe image: '+pot.image);
              } catch(e) {
                  console.log('Oops: '+e);
              }
              pot.draw();
              

              【讨论】:

              • 这依赖于caller,这是一个在严格模式下不允许的依赖于实现的扩展。
              【解决方案15】:

              利用bindcall 方法有一个更简单的方法。

              通过为对象设置私有变量,您可以利用该对象的作用域。

              示例

              function TestClass (value) {
                  // The private value(s)
                  var _private = {
                      value: value
                  };
              
                  // `bind` creates a copy of `getValue` when the object is instantiated
                  this.getValue = TestClass.prototype.getValue.bind(_private);
              
                  // Use `call` in another function if the prototype method will possibly change
                  this.getValueDynamic = function() {
                      return TestClass.prototype.getValue.call(_private);
                  };
              };
              
              TestClass.prototype.getValue = function() {
                  return this.value;
              };
              

              这种方法并非没有缺点。由于范围上下文已被有效地覆盖,因此您无法访问 _private 对象之外的内容。但是,仍然可以访问实例对象的范围并非不可能。您可以将对象的上下文 (this) 作为第二个参数传递给 bindcall,以便在原型函数中仍然可以访问它的公共值。

              访问公共值

              function TestClass (value) {
                  var _private = {
                      value: value
                  };
              
                  this.message = "Hello, ";
              
                  this.getMessage = TestClass.prototype.getMessage.bind(_private, this);
              
              }
              
              TestClass.prototype.getMessage = function(_public) {
              
                  // Can still access passed in arguments
                  // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
                  console.log([].slice.call(arguments, 1));
                  return _public.message + this.value;
              };
              
              var test = new TestClass("World");
              test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                                        // => "Hello, World" (return value)
              
              test.message = "Greetings, ";
              test.getMessage(); // []                    (console.log)
                                 // => "Greetings, World" (return value)
              

              【讨论】:

              • 为什么有人会创建原型方法的副本而不是首先创建实例化方法?
              【解决方案16】:

              您也可以尝试不直接在原型上添加方法,而是在构造函数上添加方法,如下所示:

              var MyArray = function() {
                  var array = [];
              
                  this.add = MyArray.add.bind(null, array);
                  this.getAll = MyArray.getAll.bind(null, array);
              }
              
              MyArray.add = function(array, item) {
                  array.push(item);
              }
              MyArray.getAll = function(array) {
                  return array;
              }
              
              var myArray1 = new MyArray();
              myArray1.add("some item 1");
              console.log(myArray1.getAll()); // ['some item 1']
              var myArray2 = new MyArray();
              myArray2.add("some item 2");
              console.log(myArray2.getAll()); // ['some item 2']
              console.log(myArray1.getAll()); // ['some item 2'] - FINE!
              

              【讨论】:

                【解决方案17】:

                不,没有办法。这基本上是相反的范围界定。

                在构造函数中定义的方法可以访问私有变量,因为所有函数都可以访问定义它们的范围。

                在原型上定义的方法未定义在构造函数的范围内,并且无法访问构造函数的局部变量。

                您仍然可以拥有私有变量,但如果您希望原型上定义的方法可以访问它们,您应该在 this 对象上定义 getter 和 setter,原型方法(以及其他所有方法)有权访问。例如:

                function Person(name, secret) {
                    // public
                    this.name = name;
                
                    // private
                    var secret = secret;
                
                    // public methods have access to private members
                    this.setSecret = function(s) {
                        secret = s;
                    }
                
                    this.getSecret = function() {
                        return secret;
                    }
                }
                
                // Must use getters/setters 
                Person.prototype.spillSecret = function() { alert(this.getSecret()); };
                

                【讨论】:

                • “反向作用域”是一个带有“friend”关键字的 C++ 功能。基本上任何函数都应该将它的原型定义为它的朋友。可悲的是,这个概念是 C++ 而不是 JS :(
                • 我想把这篇文章添加到我的收藏列表的顶部并保留在那里。
                • 我看不出这有什么意义——你只是添加了一个没有任何作用的抽象层。您也可以将secret 设为this 的属性。 JavaScript 根本不支持带有原型的私有变量,因为原型绑定到调用站点上下文,而不是“创建站点”上下文。
                • 那为什么不直接做person.getSecret()呢?
                • 为什么会有这么多赞?这不会使变量私有。如上所述,使用 person.getSecret() 可以让您从任何地方访问该私有变量。
                【解决方案18】:

                你不能把变量放在更高的范围内吗?

                (function () {
                    var privateVariable = true;
                
                    var MyClass = function () {
                        if (privateVariable) console.log('readable from private scope!');
                    };
                
                    MyClass.prototype.publicMethod = function () {
                        if (privateVariable) console.log('readable from public scope!');
                    };
                }))();
                

                【讨论】:

                • 然后变量在MyClass的所有实例之间共享。
                【解决方案19】:

                @凯

                那行不通。如果你这样做了

                var t2 = new TestClass();
                

                然后t2.prototypeHello 将访问 t 的私人部分。

                @AnglesCrimes

                示例代码运行良好,但它实际上创建了一个由所有实例共享的“静态”私有成员。这可能不是 morgancodes 寻找的解决方案。

                到目前为止,我还没有找到一种简单而干净的方法来做到这一点,而无需引入私有散列和额外的清理功能。可以在一定程度上模拟私有成员函数:

                (function() {
                    function Foo() { ... }
                    Foo.prototype.bar = function() {
                       privateFoo.call(this, blah);
                    };
                    function privateFoo(blah) { 
                        // scoped to the instance by passing this to call 
                    }
                
                    window.Foo = Foo;
                }());
                

                【讨论】:

                • 清楚地理解了你的观点,但你能解释一下你的代码 sn-p 试图做什么吗?
                • privateFoo 是完全私有的,因此在获得new Foo() 时是不可见的。这里只有bar()是一个公共方法,它可以访问privateFoo。您可以对简单的变量和对象使用相同的机制,但是您需要始终牢记,privates 实际上是静态的,并且将由您创建的所有对象共享。
                【解决方案20】:

                在当前的 JavaScript 中,我相当肯定有 一种 并且只有一种 方式来获得 私有状态,可以从 访问>prototype 函数,而不向this 添加任何public。答案是使用“弱地图”模式。

                总结一下:Person 类有一个弱映射,其中键是 Person 的实例,值是用于私有存储的普通对象。

                这是一个功能齐全的例子:(在http://jsfiddle.net/ScottRippey/BLNVr/玩)

                var Person = (function() {
                    var _ = weakMap();
                    // Now, _(this) returns an object, used for private storage.
                    var Person = function(first, last) {
                        // Assign private storage:
                        _(this).firstName = first;
                        _(this).lastName = last;
                    }
                    Person.prototype = {
                        fullName: function() {
                            // Retrieve private storage:
                            return _(this).firstName + _(this).lastName;
                        },
                        firstName: function() {
                            return _(this).firstName;
                        },
                        destroy: function() {
                            // Free up the private storage:
                            _(this, true);
                        }
                    };
                    return Person;
                })();
                
                function weakMap() {
                    var instances=[], values=[];
                    return function(instance, destroy) {
                        var index = instances.indexOf(instance);
                        if (destroy) {
                            // Delete the private state:
                            instances.splice(index, 1);
                            return values.splice(index, 1)[0];
                        } else if (index === -1) {
                            // Create the private state:
                            instances.push(instance);
                            values.push({});
                            return values[values.length - 1];
                        } else {
                            // Return the private state:
                            return values[index];
                        }
                    };
                }
                

                就像我说的,这确实是实现所有 3 个部分的唯一方法。

                但是,有两个警告。首先,这会降低性能——每次访问私有数据时,都是一次O(n) 操作,其中n 是实例数。因此,如果您有大量实例,您不会想要这样做。 其次,当你完成一个实例时,你必须调用destroy;否则,实例和数据将不会被垃圾收集,最终会导致内存泄漏。

                这就是为什么我想坚持原来的答案,“你不应该”

                【讨论】:

                • 如果你没有在 Person 的实例超出范围之前明确地销毁它,weakmap 是否会保留对它的引用,所以你会有内存泄漏?我想出了一个 protected 的模式,因为 Person 的其他实例可以访问该变量,而从 Person 继承的那些实例可以。只是把它弄乱了,所以不确定除了额外的处理之外是否有任何缺点(看起来不像访问私人那么多)stackoverflow.com/a/21800194/1641941返回一个私人/受保护的对象是一种痛苦,因为调用代码可以改变你的私人/受保护。
                • @HMR 是的,您必须明确销毁私有数据。我将在我的答案中添加这个警告。
                【解决方案21】:

                是的,这是可能的。 PPF 设计模式正好解决了这个问题。

                PPF 代表私有原型函数。基本 PPF 解决了这些问题:

                1. 原型函数可以访问私有实例数据。
                2. 原型函数可以私有化。

                首先,只需:

                1. 将您希望可从原型函数访问的所有私有实例变量放入单独的数据容器中,并
                2. 将对数据容器的引用作为参数传递给所有原型函数。

                就这么简单。例如:

                // Helper class to store private data.
                function Data() {};
                
                // Object constructor
                function Point(x, y)
                {
                  // container for private vars: all private vars go here
                  // we want x, y be changeable via methods only
                  var data = new Data;
                  data.x = x;
                  data.y = y;
                
                  ...
                }
                
                // Prototype functions now have access to private instance data
                Point.prototype.getX = function(data)
                {
                  return data.x;
                }
                
                Point.prototype.getY = function(data)
                {
                  return data.y;
                }
                

                ...

                在此处阅读全文:

                PPF Design Pattern

                【讨论】:

                • 仅链接的答案通常不赞成 SO。请举个例子。
                • 文章里面有例子,请看这里
                • 但是,如果稍后该站点出现故障,会发生什么?那么有人应该怎么看一个例子呢?该政策已经到位,因此链接中的任何有价值的东西都可以保留在这里,而不必依赖不受我们控制的网站。
                • @Edward,您的链接读起来很有趣!但是,在我看来,使用原型函数访问私有数据的主要原因是为了防止每个对象都使用相同的公共函数浪费内存。您描述的方法不能解决这个问题,因为对于公共使用,原型函数需要包装在常规公共函数中。我想如果你有很多 ppf 组合在一个公共函数中,这种模式可能对节省内存很有用。你还会用它们做其他事情吗?
                • @DiningPhilosofer,感谢您欣赏我的文章。是的,你是对的,我们仍然使用实例函数。但我们的想法是通过重新调用他们的 PPF 对应物来让它们尽可能轻量级,这些对应物可以完成所有繁重的工作。最终,所有实例都会调用相同的 PPF(当然是通过包装器),因此可能会节省一定的内存。问题是多少。我希望节省大量资金。
                【解决方案22】:

                您实际上可以通过使用 Accessor Verification 来实现这一点:

                (function(key, global) {
                  // Creates a private data accessor function.
                  function _(pData) {
                    return function(aKey) {
                      return aKey === key && pData;
                    };
                  }
                
                  // Private data accessor verifier.  Verifies by making sure that the string
                  // version of the function looks normal and that the toString function hasn't
                  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
                  // Function.prototype.toString before this closure executes.
                  function $(me) {
                    if(me._ + '' == _asString && me._.toString === _toString) {
                      return me._(key);
                    }
                  }
                  var _asString = _({}) + '', _toString = _.toString;
                
                  // Creates a Person class.
                  var PersonPrototype = (global.Person = function(firstName, lastName) {
                    this._ = _({
                      firstName : firstName,
                      lastName : lastName
                    });
                  }).prototype;
                  PersonPrototype.getName = function() {
                    var pData = $(this);
                    return pData.firstName + ' ' + pData.lastName;
                  };
                  PersonPrototype.setFirstName = function(firstName) {
                    var pData = $(this);
                    pData.firstName = firstName;
                    return this;
                  };
                  PersonPrototype.setLastName = function(lastName) {
                    var pData = $(this);
                    pData.lastName = lastName;
                    return this;
                  };
                })({}, this);
                
                var chris = new Person('Chris', 'West');
                alert(chris.setFirstName('Christopher').setLastName('Webber').getName());
                

                这个例子来自我关于Prototypal Functions & Private Data 的帖子,并在那里进行了更详细的解释。

                【讨论】:

                • 这个答案太“聪明”而无用,但我喜欢使用 IFFE 绑定变量作为秘密握手的答案。这个实现使用了太多的闭包而没有用处;使用原型定义方法的目的是防止为每个对象上的每个方法构造新的函数对象。
                • 这种方法使用密钥来识别哪些原型方法是可信的,哪些不是。但是,验证密钥的是实例,因此必须将密钥发送到实例。但是,不受信任的代码可能会在假实例上调用受信任的方法,从而窃取密钥。并使用该密钥,创建被真实实例信任的新方法。所以这只是默默无闻的隐私。
                【解决方案23】:

                您可以在构造函数定义中使用原型赋值。

                该变量将对原型添加的方法可见,但函数的所有实例都将访问同一个 SHARED 变量。

                function A()
                {
                  var sharedVar = 0;
                  this.local = "";
                
                  A.prototype.increment = function(lval)
                  {    
                    if (lval) this.local = lval;    
                    alert((++sharedVar) + " while this.p is still " + this.local);
                  }
                }
                
                var a = new A();
                var b = new A();    
                a.increment("I belong to a");
                b.increment("I belong to b");
                a.increment();
                b.increment();
                

                希望对你有用。

                【讨论】:

                  【解决方案24】:

                  我建议将“在构造函数中分配原型”描述为 Javascript 反模式可能是个好主意。想想看。太冒险了。

                  在创建第二个对象(即 b)时,您实际上所做的是为使用该原型的所有对象重新定义该原型函数。这将有效地重置您示例中对象 a 的值。如果您想要一个共享变量并且碰巧预先创建了所有对象实例,它会起作用,但感觉太冒险了。

                  我在我最近正在研究的一些 Javascript 中发现了一个错误,这是由于这种确切的反模式造成的。它试图在正在创建的特定对象上设置拖放处理程序,而是为所有实例执行此操作。不好。

                  Doug Crockford 的解决方案是最好的。

                  【讨论】:

                    【解决方案25】:

                    Doug Crockford's page on this。您必须使用可以访问私有变量范围的东西间接地做到这一点。

                    另一个例子:

                    Incrementer = function(init) {
                      var counter = init || 0;  // "counter" is a private variable
                      this._increment = function() { return counter++; }
                      this._set = function(x) { counter = x; }
                    }
                    Incrementer.prototype.increment = function() { return this._increment(); }
                    Incrementer.prototype.set = function(x) { return this._set(x); }
                    

                    用例:

                    js>i = new Incrementer(100);
                    [object Object]
                    js>i.increment()
                    100
                    js>i.increment()
                    101
                    js>i.increment()
                    102
                    js>i.increment()
                    103
                    js>i.set(-44)
                    js>i.increment()
                    -44
                    js>i.increment()
                    -43
                    js>i.increment()
                    -42
                    

                    【讨论】:

                    • 这个例子似乎是一种糟糕的做法。使用原型方法的意义在于,您不必为每个实例都创建一个新方法。无论如何,你正在这样做。对于每一种方法,您都在创建另一种方法。
                    • @ArmedMonkey 这个概念看起来不错,但同意这是一个不好的例子,因为显示的原型函数是微不足道的。如果原型函数是更长的函数,需要对“私有”变量进行简单的 get/set 访问,那将是有意义的。
                    • 为什么还要麻烦通过set 公开_set?为什么不直接将其命名为set
                    猜你喜欢
                    • 2023-03-23
                    • 2021-02-04
                    • 2018-07-31
                    • 2013-08-03
                    • 1970-01-01
                    • 2019-07-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-04-01
                    相关资源
                    最近更新 更多