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


187

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

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

这有效:

t.nonProtoHello()

但这不是:

t.prototypeHello()

我习惯于在构造函数中定义方法,但出于以下两个原因而逐渐远离它。



14
@ecampver,除了两年前有人问过这个问题 ....
Pacerier 2014年

Answers:


191

不,没有办法。实际上,这将是相反的范围。

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

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

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

14
“反向作用域”是带有“ friend”关键字的C ++功能。基本上,任何函数都应该将其定义为原型。可悲的是,这个概念是C ++而不是JS :(
TWiStErRob 2013年

1
我想将此帖子添加到我的收藏夹列表的顶部,并保持在该位置。
Donato

2
我看不到这一点的意思-您只添加了一层无用的抽象层。您也可以仅设置secret的属性this。JavaScript根本不支持带有原型的私有变量,因为原型绑定到了调用站点上下文,而不是“创建站点”上下文。
nicodemus13

1
为什么不person.getSecret()那么做呢?
法赫米

1
为什么会有这么多的投票?这不会使变量私有。如上所述,使用person.getSecret()可以让您从任何地方访问该私有变量。
alexr101

63

更新:使用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开发人员会如此努力使字段真正私有。


4
可悲的是,_private如果您想利用原型继承,命名约定仍然是最好的解决方案。
暗恋

1
ES6将有一个新概念,Symbol这是创建私有字段的绝佳方法。这是一个很好的解释:curiosity-driven.org/private-properties-in-javascript
Scott Rippey 2015年

1
不,您可以将Symbol整个类封闭起来。这样,所有原型方法都可以使用Symbol,但是它永远不会暴露在类之外。
Scott Rippey 2015年

2
您链接的文章说:“ 符号类似于私人名称,但是-与私人名称不同,它们不能提供真正的隐私 ” 实际上,如果您拥有该实例,则可以使用获得它的符号Object.getOwnPropertySymbols。因此,这仅仅是默默无闻的隐私。
Oriol

2
@Oriol是的,隐私非常晦涩。仍然可以遍历符号,然后可以通过推断符号的用途 toString。这与Java或C#没什么不同...私有成员仍然可以通过反射访问,但是通常会被强烈遮盖。所有这些都强化了我的最终观点:“我不明白为什么JS开发人员会如此努力使字段真正私有。”
Scott Rippey 2015年

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

我希望收到任何人通过这种方式发现错误的反馈。


4
我猜一个潜在的问题是,任何实例都可以使用不同的实例ID来访问其他任何实例的私有变量。不一定是坏事...
Mims H. Wright

15
您需要在每次构造函数调用时重新定义原型函数
Lu4 2013年

10
@ Lu4我不确定这是真的。构造函数是从闭包内部返回的;首次定义原型函数是第一次,即立即调用函数表达式。除了上面提到的隐私问题,这对我来说看起来很好(乍看之下)。
guypursey

1
@ MimsH.Wright其他语言允许访问同一类的其他对象私有对象,但仅当您引用它们时才可以访问。为此,您可以将私有对象隐藏在以对象指针作为键(与ID相对)的函数后面。这样,您只能访问您知道的对象的私有数据,这与其他语言的作用域更加一致。但是,此实现为这个更深层次的问题提供了启示。直到构造函数被使用之前,私有对象才不会被垃圾回收。
Thomas Nadin 2013年

3
我想提到的是i已添加到所有实例。因此它不是完全“透明的”,并且i仍可能被篡改。
Scott Rippey 2014年

18

请参阅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

47
这个例子似乎是可怕的做法。使用原型方法的目的是,您不必为每个实例都创建一个新方法。无论如何,您正在这样做。对于每种方法,您都将创建另一个。
2012年

2
@ArmedMonkey这个概念看起来不错,但是同意这是一个不好的例子,因为显示的原型函数很简单。如果原型函数是更长的函数,需要对“私有”变量进行简单的获取/设置访问,那将是有意义的。
pancake

9
为什么还要麻烦_set通过曝光set呢?为什么不只是起名呢set
Scott Rippey 2014年

15

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

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

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

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


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

清楚地理解了您的观点,但是您能否解释一下您的代码段打算做什么?
Vishwanath 2012年

privateFoo是完全私人的,因此在获取时看不见new Foo()bar()这里只有一个公共方法,可以访问privateFoo。您可以对简单的变量和对象使用相同的机制,但是需要始终牢记,它们privates实际上是静态的,并且将由您创建的所有对象共享。
Philzen

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设计模式


4
仅链接的答案通常在SO上不被接受。请举一个例子。
Corey Adler

本文中包含示例,因此请在此处查看
Edward

5
但是,如果该站点稍后某个时刻出现故障,会发生什么情况?那别人应该怎么看一个例子呢?制定了该政策,以便可以在此处保留链接中的任何有价值的内容,而不必依赖不受我们控制的网站。
Corey Adler

3
@Edward,您的链接很有趣!但是,在我看来,使用原型函数访问私有数据的主要原因是为了防止每个对象浪费具有相同公共函数的内存。您描述的方法不能解决此问题,因为对于公共用途而言,原型功能需要包装在常规的公共功能中。我想如果您将许多ppf组合在一个公共函数中,则该模式可能对节省内存有用。您还会将它们用于其他用途吗?
餐饮哲学家

@DiningPhilosofer,谢谢您欣赏我的文章。是的,您是对的,我们仍然使用实例函数。但是,其想法是通过重新调用完成所有繁重工作的PPF同行,使它们尽可能轻巧。最终,所有实例都调用相同的PPF(当然是通过包装器),因此可以节省一定的内存。问题是多少。我希望节省很多。
爱德华

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

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


1
这个答案太“聪明”了,无法使用,但是我喜欢使用IFFE绑定变量作为秘密握手的答案。这个实现使用了太多的闭包,没有用。使用原型定义方法的目的是防止在每个对象上为每种方法构造新的功能对象。
greg.kindel

这种方法使用秘密密钥来识别哪些原型方法是受信任的,哪些不是。但是,验证密钥的是实例,因此必须将密钥发送到实例。但是,然后,不可信的代码可以在伪造的实例上调用可信的方法,这将窃取密钥。并使用该密钥,创建新的方法,这些方法将被实际实例信任。因此,这仅仅是默默无闻的隐私。
Oriol

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

就像我说的那样,这实际上是实现所有三个部分的唯一方法。

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

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


如果您没有在Person实例超出范围之前对其进行显式销毁,那么weakmap不会保留对其的引用,这样会导致内存泄漏?我想出了一种受保护的模式,因为Person的其他实例可以访问该变量,而从Person继承的实例可以访问该变量。只是摆弄了一下,所以不确定除了额外的处理(看起来不像访问私有对象一样)之外是否还有其他弊端stackoverflow.com/a/21800194/1641941返回私有/受保护对象很麻烦,因为调用代码然后可以更改您的私人/受保护者。
HMR 2014年

2
@HMR是的,您必须显式销毁私有数据。我要将此警告添加到我的答案中。
Scott Rippey 2014年

3

利用bindand 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)

2
为什么有人会创建原型方法的副本,而不是首先创建实例方法呢?
粉碎

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

1
这取决于caller,这是严格模式下不允许的与实现相关的扩展。
Oriol

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

此实现的主要问题是,它在每个实例上都重新定义了原型。


有趣的是,我真的很喜欢这种尝试,并且一直在思考同一件事,但是您是对的,在每个实例上重新定义原型函数是一个很大的限制。这不仅是因为它浪费了CPU周期,还因为如果以后再更改原型,它将在下一次实例化时将其“重置”回到构造函数中定义的原始状态:/
Niko Bellic

1
这不仅重新定义了原型,还为每个实例定义了一个新的构造函数。因此,“实例”不再是同一类的实例。
Oriol

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


2
我认为最好不要在构造函数中使用原型,因为每次创建新实例时都会创建一个新函数。
whamsicore '16

@whamsicore是的,但是在这种情况下,这是必不可少的,因为对于每个实例化的单个对象,我们都必须安排一个共享的闭包。这就是为什么函数定义驻留在构造函数中的原因,我们不得不提到的SharedPrivate.prototypethis.constructor.prototype这不是什么大不了重新定义getP和SETP多次...
热度

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

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

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


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

4
然后,变量在MyClass的所有实例之间共享。
粉碎

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!

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

0

我今天遇到了完全相同的问题,在详细说明了Scott Rippey的一流回答后,我想出了一个非常简单的解决方案(IMHO),该解决方案既与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测试。我很想听听您的意见。


这里是参考:检查“更近一步”部分。philipwalton.com/articles/…–
jimasun

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

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


不能以有用的方式回答问题。您为什么认为这是答案?如何运作?仅仅告诉某人在没有任何上下文或含义的情况下更改他们的代码并不能帮助他们了解自己的错误。
GrumpyCrouton

他想要一种通过原型访问类的隐藏私有变量的方法,而不必在该类的每个实例上都创建该隐藏私有变量。上面的代码是执行此操作的示例方法。那怎么不是这个问题的答案呢?
dylan0150

我不是说这不是问题的答案。我说这不是一个有用的答案,因为它没有帮助任何人学习。您应该解释您的代码,它为什么起作用,为什么它是正确的方法。如果我是问题的作者,我不会接受您的回答,因为它不会鼓励学习,也不会教我我做错了什么,给定的代码在做什么或如何工作。
GrumpyCrouton

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

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

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函数,该函数返回构造函数,该构造函数返回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带有单个函数的函数$,该函数进行检查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尽管可以在构造函数的函数体中定义非原型函数,但没有意义。所有功能均以public dollar模式 调用this.$("functionName"[, param1[, param2 ...]])。使用私有美元模式 访问私有值this._$("valueName"[, replacingValue]);。由于Interface没有的定义_$,因此外部对象无法访问这些值。由于每个原型函数的主体this都设置为valuesfunction中的对象,因此$,如果直接调用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

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


0

ES6弱地图

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

注意:通过让Garbage Collector识别并丢弃未使用的实例,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;
}());

有关此模式的详细说明,请参见此处


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

this.privateField不会是私人领域。它可以从外面访问:t.privateField
V. Rubinetti

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

我希望这会有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.