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

IT技术 javascript private-members
2021-01-30 19:30:53

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

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

这有效:

t.nonProtoHello()

但这不会:

t.prototypeHello()

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

6个回答

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

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

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

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

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };
“反向作用域”是带有“friend”关键字的 C++ 特性。基本上任何函数都应该定义它的原型作为它的朋友。遗憾的是,这个概念是 C++ 而不是 JS :(
2021-03-11 19:30:53
我想将此帖子添加到我的收藏夹列表的顶部并保留在那里。
2021-03-11 19:30:53
为什么会有这么多赞?这不会使变量私有。如上所述,使用 person.getSecret() 可以让您从任何地方访问该私有变量。
2021-03-19 19:30:53
我不明白这有什么意义——你只是添加了一个什么都不做的抽象层。你可能也只是做出secret的一个属性thisJavaScript 根本不支持带有原型的私有变量,因为原型绑定到调用站点上下文,而不是“创建站点”上下文。
2021-04-02 19:30:53
为什么不这样做person.getSecret()呢?
2021-04-05 19:30:53

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

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

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

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

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

请参阅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
为什么还要费心暴露_setvia set为什么不直接命名呢set
2021-03-14 19:30:53
这个例子似乎是可怕的做法。使用原型方法的目的是让您不必为每个实例都创建一个新方法。反正你是这样做的。对于每种方法,您都在创建另一种方法。
2021-03-21 19:30:53
@ArmedMonkey 这个概念看起来很合理,但同意这是一个糟糕的例子,因为显示的原型函数是微不足道的。如果原型函数是更长的函数,需要对“私有”变量进行简单的获取/设置访问,那将是有意义的。
2021-03-21 19:30:53

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

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

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

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