194

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

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

这有效:

t.nonProtoHello()

但这不会:

t.prototypeHello()

我习惯于在构造函数中定义我的方法,但由于几个原因而不再使用它。

4

25 回答 25

200

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

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

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

您仍然可以拥有私有变量,但如果您希望原型上定义的方法可以访问它们,您应该在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()); };
于 2009-01-12T17:08:30.230 回答
65

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

长话短说,您可以使用 newSymbol创建私有字段。
这是一个很好的描述: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 开发人员如此努力地使字段真正私有。

于 2014-02-03T08:34:19.033 回答
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);

我希望任何看到这种方式有错误的人提供一些反馈。

于 2012-12-13T09:31:04.837 回答
19

请参阅Doug Crockford 的页面。您必须使用可以访问私有变量范围的东西间接地做到这一点。

另一个例子:

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
于 2009-01-12T17:08:52.910 回答
15

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

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

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

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

于 2010-11-26T12:12:39.660 回答
10

@凯

那是行不通的。如果你这样做

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;
}());
于 2012-02-24T08:00:39.813 回答
6

是的,这是可能的。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 设计模式

于 2013-11-19T20:49:08.637 回答
5

您实际上可以通过使用访问器验证来实现这一点:

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

这个例子来自我关于原型函数和私有数据的帖子,在那里有更详细的解释。

于 2013-02-07T19:52:06.883 回答
5

在当前的 JavaScript 中,我相当肯定只有一种方法可以拥有私有 state,可以从原型函数访问,而无需添加任何public到. 答案是使用“弱地图”模式。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; 否则,实例和数据将不会被垃圾收集,最终会导致内存泄漏。

这就是为什么我最初的回答“你不应该”是我想要坚持的原因。

于 2014-02-16T09:42:48.030 回答
3

通过利用bind和方法,有一种更简单的call方法。

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

例子

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)
于 2015-02-20T12:18:21.523 回答
3

尝试一下!

    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();
于 2015-02-26T17:52:24.333 回答
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

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

于 2015-08-21T12:40:16.193 回答
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 原型是黄金。

于 2016-03-05T14:49:28.733 回答
1

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

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

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

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

于 2017-04-10T00:46:58.350 回答
0

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

(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!');
    };
}))();
于 2014-11-21T23:22:27.990 回答
0

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

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!
于 2015-01-27T06:09:51.037 回答
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();
于 2016-02-18T12:52:14.257 回答
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 测试。我渴望阅读您的意见。

于 2016-06-06T01:50:34.947 回答
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()

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

于 2017-06-08T16:18:50.473 回答
0

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

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

  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);
};
于 2017-06-21T11:51:15.180 回答
0

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

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

   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')
于 2017-09-10T12:34:57.873 回答
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函数。该接口的唯一方法是接收参数以调用构造函数对象中的相应函数,之后传递的任何附加参数都将在调用中传递。$namename

全局定义的辅助函数根据需要ClassValues将所有字段存储在对象中。它定义了_$访问它们的函数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作为作用域对象调用。传递给的附加参数$将在函数调用时传递。

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都设置为 function 中的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

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

于 2019-01-31T05:21:25.310 回答
0

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

可以在此处找到有关此模式的更详细说明

于 2019-06-24T10:56:28.627 回答
-1

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

  1. 替换var privateField = "hello"this.privateField = "hello"
  2. 在原型中替换privateFieldthis.privateField.
  3. 在非原型中也替换privateFieldthis.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()
于 2017-12-29T02:12:52.317 回答
-2

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

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

我希望这会很有用。

于 2009-11-24T13:48:55.853 回答