【问题标题】:JavaScript Module Pattern - Private variables vs. Static variablesJavaScript 模块模式 - 私有变量与静态变量
【发布时间】:2014-02-18 17:55:30
【问题描述】:

这是著名的 JavaScript 模块模式的一个例子:

var Person = (function() {

    var _name; // so called 'private variable'

    function Person(name) {
        _name = name;
    }

    Person.prototype.kill = function() {
        console.log(_name + ' has been shot');
    };

    return Person;
})();

var paul = new Person('Paul');
paul.kill();

到目前为止还不错吧?这会将'Paul has been shot' 记录到控制台,这正是我们想要的。

但是。

_name 真的是私有变量吗?我会将私有变量定义为属于对象实例的变量,外部世界无法访问该变量。最后一部分有效,我无法从闭包外部访问_name

但如果我这样做:

var paul = new Person('Paul');
var bran = new Person('Bran');
paul.kill();
bran.kill();

这将记录'Bran has been shot',两次。那里没有保罗。所以_name 实际上与我的 Person 对象的所有实例共享。这就是我将其定义为“静态变量”的内容,尽管它也无法从外部访问。

那么有没有办法用模块模式创建一个真正的私有成员变量?一种不是静态的。

也经常发生的事情是在构造函数内部定义this._name,但这会杀死私有部分,现在可以从外部访问:

function Person(name) {
    this._name = name;
}

var bran = new Person();
console.log(bran._name); // yep, accessible

问题:

所以。私有不是真正私有的,只是静态的。我们如何使用模块模式创建一个真正的私有成员变量?一个属于实例的变量,它不是静态的,一个不能从外部访问的变量。

【问题讨论】:

标签: javascript modularity


【解决方案1】:

你是对的; _name 更像是一个静态变量。它保存在包含构造函数的闭包中,因此每次使用构造函数都将使用相同的变量。请记住,这与类无关,而与闭包和函数有关。它可能非常方便,但这不是您处理私人成员的方式。

不出所料,Douglas Crockford 有一个page devoted to private members in Javascript

为了成为私人成员,您必须“更深一层”。不要在构造函数之外使用闭包;使用 构造函数作为闭包。构造函数内部定义的任何变量或方法显然都不能被外界使用。事实上,对象也无法访问它们,因此它们非常“私有”。

不过,我们想使用我们的私人会员。 :) 那该怎么办?

好吧,在构造函数中,这样做:

var Klass = function () {
    var private = 3;
    this.privileged = function () { return private; };
};

然后:

var k = Klass();
console.log(k.privileged()); // 3

看看它是如何使用构造函数作为闭包的? this.privileged 继续存在,附加到对象上,因此privatethis.privileged 的闭包中继续存在。

不幸的是,Javascript 中的私有和特权方法存在一个问题。每次都必须从头开始实例化它们。没有代码共享。这显然是我们想要的私有成员,但它并不适合方法。使用它们会减慢对象实例化并使用更多内存。当/如果您遇到效率问题时,请记住这一点。

【讨论】:

  • 感谢您的精彩回答。而且,与公共成员不同,您不能在对象构建完成后添加私有成员。
【解决方案2】:

“真正的私有成员变量”和基于原型的方法不能很好地结合在一起。实现您想要的唯一方法是在构造函数中创建所有方法。

var Person = (function() {

    function Person(name) {
        this.kill = function() {
            console.log(name + ' has been shot');
        };
    }

    return Person;
})();

var paul = new Person('Paul');
var bran = new Person('Bran');
paul.kill(); // Paul has been shot
bran.kill(); // Bran has been shot

但这会使用更多的内存并且速度会变慢,因为每个实例都有一个唯一版本的 kill 函数。

通常,下划线前缀用于半私有实例属性,只要暴露的数据不存在安全风险。大多数 JavaScript 代码使用者都知道不要乱用下划线前缀属性。

Some more reading you may find useful is here.

【讨论】:

  • +1 for “真正的私有成员变量”和基于原型的方法不能很好地结合在一起
【解决方案3】:

问题在于您的 _name 变量在 Person 范围之外,并且在所有 Person 实例之间共享。改为执行以下操作:

(function() {

    var Person = function(name) {
         var _name = name; // so called 'private variable'

        this.getName = function()
        {
            return _name;
        }

        this.kill = function()
        {
            console.log(this.getName() + ' has been shot');
        }
    }

    /*Alternative
    Person.prototype.kill = function() {
        console.log(this.getName() + ' has been shot');
    };*/

    window.Person = Person;
})();

var paul = new Person('Paul');
var bran = new Person('Bran');
paul.kill();
bran.kill();

【讨论】:

  • 为了简单起见,我使用了基本的 window.class 示例。但如果你正在做一些大事,我建议使用 RequireJs。
【解决方案4】:

需要在实例范围内私有的变量必须在该范围内,例如:

function Person(name) {
    var _name = name;

    this.kill = function() {
      console.log( _name + ' has been shot' ); 
    }
}

这样做的一个缺点是 kill 必须在一个也可以访问私有变量的范围内定义。

【讨论】:

    【解决方案5】:

    为了创建一个私有变量,你应该把它放在构造函数中,所以你的代码是这样的:

    var Person = (function() {
    
        function Person(name) {
            // since name is a param, you don't even have to create a _name var
            // and assign name to it
            this.getName = function () {
                return name;
            };
        }
    
        Person.prototype.kill = function() {
            console.log(this.getName() + ' has been shot');
        };
    
        return Person;
    })();
    
    var paul = new Person('Paul');
    paul.kill();
    

    你也可以在构造函数中声明.kill

    var Person = (function() {
    
        function Person(name) {
            this.kill = function() {
                console.log(name + ' has been shot');
            };
        }
    
        return Person;
    })();
    
    var paul = new Person('Paul');
    paul.kill();
    

    【讨论】:

    • 虽然name 在技术上是私有变量,但getName() getter 函数有效地将其值公开。所以这并没有真正的帮助。
    • 它确实是公开的,但只能读取和锁定写入。
    • 如果可变性是您唯一关心的问题,您可以使用Object.defineProperty with writable: false
    • 我同意,我只是想向他展示实现目标的不同方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-08
    • 1970-01-01
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多