Javascript模块模式和new关键字

8
我了解模块模式的基础知识及其使用封闭包来允许私有成员,但我无法完全理解下面的代码为什么会产生这样的结果:
var Calculator = function() {
    var priv = 0;

    return {
        changePriv: function() { priv++;},
        printPriv: function() { console.log(priv);}
    }
}

var myCalc = Calculator();
myCalc.printPriv();
myCalc.changePriv();
myCalc.printPriv();

var myOtherCalc = Calculator();
myCalc.printPriv();

控制台输出为:
0
1
1

因此,有意地省略了这里的new关键字,第一个调用将myCalc设置为一个Calculator对象。它开始时具有0的priv值,然后递增并打印出其新的priv值为1。

但是a)为什么下一次对 Calculator()的调用最终会返回指向相同对象的引用(如第二个“1”所示)?我知道我可以在这里使用new并避免这种情况,但我不明白为什么我必须这样做。难道这个函数不是使用对象字面量语法来创建一个新对象,然后返回它吗? b)既然它似乎正在使用相同的函数堆栈空间(这是否是在JS中考虑它的正确方式?),为什么在返回到同一对象之前不清除priv变量呢?

编辑:更正了疏忽/愚蠢的错误(感谢scessor),即使没有使用new关键字,它现在也输出一个新/不同的计算器对象。因此,a)和b)得以澄清。我的问题是“在调用模块模式构造函数时是否使用new无关紧要。答案是,我想不重要吧(?)。(Joseph:请参见http://jsfiddle.net/MvMvy/5/ ... instanceof运算符无论哪种方式使用模块模式都不起作用。)


2
按照惯例,以大写字母开头的函数被保留用于构造函数(即那些应该使用 new 调用的函数)。Calculator 函数明确返回一个对象,因此使用 new 调用它对其返回值没有任何影响。 - RobG
谢谢RobG。这正是我希望得到确认的内容。 - user718147
3个回答

9

您没有输出另一个计算器myOtherCalc:如果要进行比较,将第三个myCalc.printPriv();替换为:

myOtherCalc.printPriv();

然后输出结果为:
0
1
0

3

在你的情况下,你不需要使用new

通常情况下,如果你使用new,你期望得到的是你调用的构造函数的实例。在你的情况下,这是不会发生的,因为你手动返回了一个对象。这样做没有意义,而且稍后如果你混淆了使用方法,将会导致问题。很快你就可能会对你的对象进行"实例测试",并遇到这种"不匹配"的情况。

而且你的代码中有个拼写错误:

var myCalc = Calculator();       //create calculator
myCalc.printPriv();              //"myCalc" private is 0
myCalc.changePriv();             //increment
myCalc.printPriv();              //"myCalc" private is 1

var myOtherCalc = Calculator();  //another calculator
myCalc.printPriv();              ///but you printed "myCalc" again

“must not”和“should not”是不同的陈述。在他的具体例子中,我会说他不应该这样做,但是……有一些模块模式的版本可以很好地与“new”配合使用,并且不会遇到您所描述的instanceof问题。请参见:http://carldanley.com/js-module-pattern/ - Frug

0

与“new”操作符无关... 在这里,您可以获得有关proto/constructor的详细解释: http://en.wikibooks.org/wiki/JavaScript/Access_Control

然而,这只是一个无意义的例子,您可以通过getter和setter方法访问priv:

function Calculator2() {
var priv = 0;
this.public = 0;
this.getPriv = function(){
    return  priv;
}
this.setPriv = function(val){
    priv = val;
}
}
Calculator2.prototype.changePriv = function(){
this.setPriv(this.getPriv()+1);
}
Calculator2.prototype.printPriv = function(){
    console.log("priv = " + this.getPriv());
}
Calculator2.prototype.changePublic = function(){
    this.public++;
}
Calculator2.prototype.printPublic = function(){
    console.log(this.public);
}

在此情况下,变量 priv 始终可以通过 getter 和 setter 方法访问,
在下一个示例中,您有一个私有变量 className 和另一个公共变量 __className:
<div id = "outputDiv" style="width:600px;height:400px;border:solid 1px #000"></div>
<script type="text/javascript">
    //<![CDATA[

//脚本: var SomeClass = function(className) {

    var __className__ = className;
    this.__className__ = "\"public default className\"";
    var someString = new String("");

    this.setScopeText = function() { // void
        someString = "A new instance of \"private [__classname__] : " +
        __className__ + "\"" +
        " has been created. Refering to [__className__]<br />" +
        "A new instance of " +
        this.__className__ +
        " has been created. Refering to [this.__className__]";
        return someString;
    };

    this.getScopeText= function (){
        return someString;
    }

    this.setOutput = function(elementId, someString){
        var outputPane = this.getSomePane(elementId);
        outputPane.innerHTML += "<p>" + someString + "</p>";
    }

    this.getSomePane = function(elementId){
        var outputP = document.getElementById(elementId);
        return outputP;
    }

}

SomeClass.prototype.changeClassNameVariable = function( str ){
    this.__className__  = str;
}

// 结束声明。

// 测试:

var sc = new SomeClass("foo");

sc.setOutput("outputDiv",sc.__className__);
sc.setOutput("outputDiv",sc.setScopeText());
sc.setOutput("outputDiv",sc.getSomePane("outputDiv"));

sc.__className__ = "\"Some name\"";
sc.setOutput("outputDiv",sc.__className__);
sc.setOutput("outputDiv",sc.setScopeText());

sc.changeClassNameVariable("bar");
sc.setOutput("outputDiv",sc.__className__);
sc.setOutput("outputDiv",sc.setScopeText());

// 结束 JavaScript 和 CDATA 部分

//]]>
</script>

在 "div:outputDiv" 中输出:

"public default className"

已创建一个新的实例 "private [classname] : foo",引用 [className] 已创建一个新的实例 "public default className",引用 [this.className]

[object HTMLDivElement]

"Some name"

已创建一个新的实例 "private [classname] : foo",引用 [className] 已创建一个新的实例 "Some name",引用 [this.className]

bar

已创建一个新的实例 "private [classname] : foo",引用 [className] 已创建一个新的实例 "bar",引用 [this.className]

-> 在构造函数中声明的 className 永远不会改变! -> this.className 或 SomeClass.prototype.className 是公共的,可以更改。

我希望这可以更清楚地理解链和我的注释...


你的示例不允许私有方法,这正是模块模式的重点。(getPriv 可以在任何地方访问 - 请参见 http://jsfiddle.net/VjnGq/1/) - user718147
这就是我说“这只是一个无意义的例子”的原因,只是为了展示它的工作原理... 如果不调用getPriv()方法,getPriv是可访问的,但priv不可访问... - tatactic

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