如何在构造函数中设置JavaScript私有变量?

18

假设我有一个名为 Foo 的 JavaScript 函数/类,它有一个名为 bar 的属性。我希望在实例化该类时提供 bar 的值,例如:

var myFoo = new Foo(5);

myFoo.bar设置为5。

如果我将bar设置为公共变量,那么这将起作用,例如:

function Foo(bar)
{
    this.bar = bar;
}

但如果我想把它设为私有,例如:

function Foo(bar)
{
   var bar;
}

那么我该如何设置私有变量bar的值,以便它可以在foo的所有内部函数中使用?


就我个人而言,为了避免代码变得更加复杂,我会提供适当的文档说明来替代真正意义上的私有变量和原型的使用。具体可参考正确的文档规范 - Felix Kling
@FelixKing 这是真的吗? - Melab
7个回答

56

关于JavaScript中private和protected访问控制的最佳教程在这里:http://javascript.crockford.com/private.html

function Foo(a) {
    var bar = a;                              // private instance data

    this.getBar = function() {return(bar);}   // methods with access to private variable
    this.setBar = function(a) {bar = a;}
}

var x = new Foo(3);
var y = x.getBar();   // 3
x.setBar(12);
var z = x.bar;        // not allowed (x has no public property named "bar")

10
谁给这个点踩了,能解释一下为什么吗?在我看来这是正确的。+1 - Paul
@PaulP.R.O. 这显然是一个 -1,因为每次实例化 Foo 类时,getBar 和 setBar 也会被重新定义,这显然是低效的。要定义方法,必须在构造函数(即 Foo 函数)外部使用 prototype 属性。 - Gabriel Llamas
3
我认为您没有理解这段代码的要点。如果要使bar真正私有,以至于无法从外部访问(这是OP所要求的),那么必须像我所做的那样声明它。并且,如果您像我所做的那样声明bar,那么就不能从原型中定义的方法中访问它。您必须在bar的作用域内定义函数。是的,如果您实例化了很多Foo()对象,这可能不是非常高效,但这是实现私有bar的方法,这也是提出的问题。请撤销您的反对票。 - jfriend00
7
这种方法并没有坏的做法。这只是一种权衡。为了实现隐私,您在创建对象时会受到微小的性能影响。一旦创建了对象,它就可以完美地运行。原型存在是为了方便。您无需使用它来实现您的目标。如果您正在创建大量的Foo()对象并且性能至关重要,那么这将是一个糟糕的折衷方案。但是,如果您只创建了几个或这种方法的性能非常好,而且您想要变量的隐私性,这种方法是一个很好的实践。 - jfriend00
2
@GabrielLlamas - 通过这种方式获得隐私并不是黑客行为。它使用了语言支持的特性(闭包)来解决原本没有设计到语言中的问题。这不是黑客行为,而是一种创造性的解决方案,如果需要其提供的功能,则绝对可以使用。 - jfriend00
显示剩余3条评论

24

你必须把所有需要访问私有变量的函数放在构造函数内:

function Foo(bar)
{
  //bar is inside a closure now, only these functions can access it
  this.setBar = function() {bar = 5;}
  this.getBar = function() {return bar;}
  //Other functions
}

var myFoo = new Foo(5);
myFoo.bar;      //Undefined, cannot access variable closure
myFoo.getBar(); //Works, returns 5

4
function Foo(b)
{
   var bar = b;

   this.setBar = function(x){
        bar = x;
   }

   this.alertBar = function(){
        alert(bar);
   }
}

var test = new Foo(10);
alert(test.bar); // Alerts undefined
test.alertBar(); // Alerts 10

+1 很好的例子。但还有一个问题让我困惑。是什么使得 var bar = b; 中的 bar 持久存在? - Gary
2
嗨,@Gary,希望我能解释清楚:每次构造一个 new Foo 时,在该 Foo 构造函数的函数作用域内创建一个新的 bar。只要 Foo 在内存中存在(或者任何访问 bar 的函数在 Foo 中存在引用),那么 bar 就会在内存中存在。 - Paul
1
就像每个 Foo 实例都有自己的 setBaralertBar 一样,它也有自己的 bar,实际上还有自己的 b。请参见此 JSFiddle 获取稍微更简洁的版本:http://jsfiddle.net/fzXZ6/4/。 - Paul

4

我能想到的一种方法是使用闭包,将其分配给一个名称并返回一个新对象。您可以通过调用闭包来传递任何参数到构造函数中。这将类似于以下内容:

var fooFactory = function (a, b) {
    var c = 5,
        d = 6,
        foo;

    foo = function (a, b) {
        this.a = a;
        this.b = b;
        this.bar();
    }

    foo.prototype.bar = function () {
        //do something with c and d
        this.c = c + d;
    }

    foo.prototype.getC = function () {
        return c;
    }

    foo.prototype.getD = function () {
        return d;
    }

    return new foo(a, b);
};

这样,a和b总是唯一声明的。然后,您可以像下面这样构造您的对象:

var obj = fooFactory(1, 2);
//obj contains new object: { a: 1, b: 2, c: 11 }

console.log(obj.getC());
//returns 5

我投票支持这个答案,但后来意识到它是错误的。每次调用“fooFactory”时,所有原型方法都会从头开始重新创建。因此,与直接将方法添加到对象中相比,并没有任何优势,这只会导致混淆。 - Serg

4
如果您愿意使用ES2015类,那么在ESNext中,您可以像这样使用Javascript私有变量:(参见此处)
class Foo {
  #bar = '';
  constructor(val){
      this.#bar = val;
  }
  otherFn(){
      console.log(this.#bar);
  }
}

私有字段 #bar 在 Foo 类外部无法访问。


0
在ES6+中,正确的方法如@Nitin Jadhav's answer所示。然而,如果出于某种原因你想坚持使用老式的构造函数,也可以通过以下方式实现;
function Foo(val){
  function Construct(){};
  Construct.prototype = { set bar(_){} // you may throw an error here
                        , get bar(){return val;}
                        };
  return new Construct();
};

这里发生了两件事情。

  1. 您没有像接受的答案中那样填充Foo实例的属性。
  2. 私有类字段抽象不同,当有人尝试通过setter访问私有变量时,您可以自由地抛出错误或不抛出错误。

也许您想要一个实例本身的私有字段,而不是通过原型访问它。那么您可以这样做;

function Foo(val){
  Object.defineProperty(this,"bar",{ set: function(){}
                                   , get: function(){return val;}
                                   });
};

-2

我最近遇到了类似的问题,但是我想使用访问器属性。下面是一个基于我的解决方案的Foo(Bar)示例。这个示例很简单,但可以使用更复杂的get/set函数进行扩展。

function Foo(Bar){
    Object.defineProperty(this,"bar",{get:function(){return Bar},set:function(val){Bar=val}});

}
x=new Foo(3);
y=x.bar; //3
x.bar++; //x.bar==4

@Gary JavaScript是大小写敏感的。Bar和bar是两个不同的属性。Bar是私有的,属于Foo,而bar是一个公共属性,提供对Bar的访问。这与被接受的答案达到了相同的效果。 - Peter Schweitzer Jr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接