从原型定义的函数访问私有成员变量

时间:2009-01-12 16:59:59

标签: javascript private-members

有没有办法制作“私有”变量(在构造函数中定义的变量),可用于原型定义的方法?

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

这有效:

t.nonProtoHello()

但这不是:

t.prototypeHello()

我习惯在构造函数中定义我的方法,但是由于一些原因我正在远离它。

25 个答案:

答案 0 :(得分:179)

不,没有办法做到这一点。这基本上是反过来的范围。

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

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

您仍然可以拥有私有变量,但是如果您希望原型上定义的方法可以访问它们,您应该在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()); };

答案 1 :(得分:61)

更新:使用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的现代浏览器:

您可以只使用Closures

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

或者您只能使用Prototypes

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

不要打扰将Closures与Prototypes混合

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

使用闭包访问私有变量时,原型方法无法访问变量。因此,您必须将闭包暴露在this上,这意味着您以某种方式公开它。这种方法几乎没有什么好处。

我选择哪个?

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

如果你需要原型继承 - 继承,性能等 - 那么坚持使用“_private”命名约定,而不用担心闭包。

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

答案 2 :(得分:31)

当我读到这篇文章时,这听起来像是一个艰难的挑战,所以我决定想办法。我想出的是 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);

我喜欢任何通过这种方式看到错误的人的反馈。

答案 3 :(得分:19)

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

答案 4 :(得分:15)

我建议将“在构造函数中进行原型赋值”描述为Javascript反模式可能是个好主意。想一想。风险太大了。

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

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

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

答案 5 :(得分:10)

@Kai

那不行。如果你这样做

var t2 = new TestClass();

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

@AnglesCrimes

示例代码工作正常,但它实际上创建了一个由所有实例共享的“静态”私有成员。这可能不是摩根代码所寻求的解决方案。

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

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

答案 6 :(得分:6)

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

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

  1. 原型函数可以访问私有实例数据。
  2. 可以将原型功能设为私有。
  3. 首先,只需:

    1. 将您想要从原型函数访问的所有私有实例变量放在单独的数据容器中,并
    2. 将对数据容器的引用作为参数传递给所有原型函数。
    3. 就这么简单。例如:

      // 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

答案 7 :(得分:4)

您可以使用访问者验证

来实现此目的
(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的帖子,并在那里有更详细的解释。

答案 8 :(得分:4)

在当前的JavaScript中,我非常确定一个只有一种方式可以私有状态,可以从原型功能,无需向this添加任何公开。答案是使用“弱地图”模式。

总结一下: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;否则,实例和数据将不会被垃圾收集,并且最终会导致内存泄漏。

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

答案 9 :(得分:3)

通过使用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)

答案 10 :(得分:2)

试试吧!

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

答案 11 :(得分:1)

这就是我想出来的。

(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

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

答案 12 :(得分:1)

有一种非常简单的方法可以做到这一点

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原型是金色的。

答案 13 :(得分:1)

I'm late to the party, but I think I can contribute. Here, check this out:

// 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

I call this method accessor pattern. The essential idea is that we have a closure, a key inside the closure, and we create a private object (in the constructor) that can only be accessed if you have the key.

If you are interested, you can read more about this in my article. Using this method, you can create per object properties that cannot be accessed outside of the closure. Therefore, you can use them in constructor or prototype, but not anywhere else. I haven't seen this method used anywhere, but I think it's really powerful.

答案 14 :(得分:0)

这是我在尝试为这个问题找到最简单的解决方案时想出来的东西,也许这对某人有用。我是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();

答案 15 :(得分:0)

我今天遇到了完全相同的问题,在详细阐述了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进行测试。我渴望阅读你的意见。

答案 16 :(得分:0)

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

这是怎么回事?使用私人访问者。只允许你获取变量而不是设置变量,具体取决于用例。

答案 17 :(得分:0)

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

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

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

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

答案 18 :(得分:0)

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

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

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

答案 19 :(得分:0)

我知道自从被问到已经有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根据需要将所有字段存储在一个对象中。它定义了_$函数以通过name访问它们。这遵循较短的获取/设置模式,因此如果传递value,它将用作新的变量值。

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

      return values[name];
    }
  };
};

全局定义的函数Interface接收一个对象和一个Values对象,以返回一个带有单个函数_interface的{​​{1}},该函数检查$来找到一个以参数obj命名的函数,并以name作为作用域对象调用它。传递给values的其他参数将在函数调用时传递。

$

在下面的示例中,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函数的结果ClassDefinitionConstructor可以接收任意数量的参数。 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");

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

答案 20 :(得分:0)

ES6弱地图

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

  

注意:WeakMaps的使用通过让Garbage Collector识别并丢弃未使用的实例来保证防止内存泄漏的安全性

// 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

答案 21 :(得分:-1)

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

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

答案 22 :(得分:-1)

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

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!

答案 23 :(得分:-1)

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

  1. var privateField = "hello"替换为this.privateField = "hello"
  2. 在原型中将privateField替换为this.privateField
  3. 在非原型中,还将privateField替换为this.privateField
  4. 最终代码如下:

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

答案 24 :(得分:-3)

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

变量对原型添加方法可见,但函数的所有实例都将访问相同的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();

我希望这可以有用。