有关从JavaScript中的闭包返回对象文字的详细信息

时间:2012-10-29 21:34:35

标签: javascript closures google-closure-compiler

背景:我想重写一个库(我没写过),以避免Closure Compiler使用高级选项生成警告。根据这个问题JavaScript “this” keyword and Closure Compiler warnings,答案是使用闭包重写代码。目的是避免使用关键字this(生成编译器警告)。

由于库有许多函数,我认为新闭包最好返回一个对象文字。我想了解这是如何工作的以及任何可能的后果。因此,我将以下(无意义的)示例写成了学习经验(也在这里:jsFiddle):

  var CurrencyObject = function(Amount) {
        var money = Amount;
        return {
              "toCents": function() {
                    money *= 100;
                    return money;
              },
              "toDollars": function() {
                    money /= 100;
                    return money;
              },
              "currentValue": money  // currentValue is always value of Amount
        };
  }; // end currencyObject

  var c1 = CurrencyObject(1.99); // what's the difference if the syntax includes `new`?

  alert('cents = ' + c1.toCents() + '\ncurrent money = ' + c1.currentValue + '\ndollars = ' + c1.toDollars() + '\ncurrent money = ' + c1.currentValue);

  var c2 = CurrencyObject(2.99);

  alert('cents = ' + c2.toCents() + '\ncurrent money = ' + c2.currentValue + '\ndollars = ' + c2.toDollars() + '\ncurrent money = ' + c2.currentValue);

  alert('cents = ' + c1.toCents() + '\ncurrent money = ' + c1.currentValue + '\ndollars = ' + c1.makeDollars() + '\ncurrent money = ' + c1.currentValue);

Q1 :为什么在调用 toCents 后未更新 currentValue ? (我猜这是因为 currentValue 是一个文字(?),它在第一次执行CurrencyObject时被初始化。如果是这样的话,那么返回属性 CurrentValue的?)

Q2 :此语法(使用newvar c1 = new CurrencyObject(1.99);不会以我能检测到的方式更改代码的行为,但我认为存在差异。它是什么?

Q3 :实例化 c2 时,是正在创建的功能的副本,还是 c1 c2 共享相同的(功能)代码? (如果正在创建函数的副本,我会做出哪些更改以避免这种情况?)

TIA

BTW:如果有人想知道,引用对象文字中的符号是为了避免让Closure-Compiler重命名它们。

2 个答案:

答案 0 :(得分:3)

Q1:您创建了两个包含Amount单独副本的变量。一个是在闭包中捕获的money变量中。另一个副本位于返回对象的currentValue属性中。这些是彼此没有关联的单独变量,而currentValue的初始值是使用money变量的值初始化的。

要解决此问题,您必须确定只有一个地方存储您的currentValue并在那里引用它。您只能在闭包中使用money局部变量,但这需要将currentValue属性更改为getter函数(检索money值)而不是直接数据属性

或者,您可以删除money闭包变量并仅使用currentValue属性。这需要您使用thistoCentstoDollars方法中获取该属性。在我看来,更简洁的方法是后者(使用this引用对象自己的属性)。我不知道为什么关闭不会让你这样做。

Q2:当您从构造函数中显式返回一个对象时,不再重视构造函数是作为函数直接调用还是使用new运算符调用。正如您所观察到的,两者都会产生相同的结果。

Q3:由于您的函数是匿名的,每个新的实例化都会创建一个新的匿名函数对象。效率或低效率取决于javascript实现。通过将函数声明为本地命名函数并引用该名称,可以使只有一个函数。然后,您可以保证没有创建新功能。

答案 1 :(得分:1)

<强>更新
branched your fiddle,并添加了几个实际上将共享的方法(即:不会为每个实例创建新的函数对象):例如,将金额转换为欧元或英镑的方法。我也省略了money变量,因为它根本就没有必要。为了避免必须尽可能多地使用this,我还将返回的对象分配给闭包范围内的变量,以便所有方法都可以通过该变量名引用其父对象,而不必依赖this。但是,共享方法仍然需要偶尔使用this,因为它们是在“更高”范围内声明的,而不是有权访问returnObject变量,因为它的范围不存在。提起returnObject变量声明是没有解决方案,以防万一你想知道,因为那样你很快就会发现你不能创建超过1个货币对象的实例。
最后,我已经将“构造函数”的名称更改为以小写字母开头。从技术上讲,你的函数不再是一个构造函数,并且惯例是因此它不能以大写字母开头......如果我在这里解释的任何内容,或者我建议的任何修改仍然不清楚,请告诉我

currentValue未更新,因为您通过执行以下操作更改了money变量的值:money *= 100;。此语句将money值乘以并将其分配给相同的变量。由于这是一个原语,currentValue有自己的副本这个值,它们没有任何关联。
建议:使用return money/100;来保持值money常数。现在,调用toCents方法两次与将原始数量乘以10,000相同。要在每次调用时将currentValue更新/设置为您想要的任何内容,请添加:this.currentValue = money*100;,这有点危险,或者通过使用命名引用为您的闭包访问自己的文字(哪个更安全,但更冗长一点):

var someClosure = function (amount)
{
    var money = amount;//redundant: you can use amount, it's preserved in this scope, too
    var me = {currentValue:amount,
              toCents: function()
              {
                  me.currentValue = money*100;//<-- use me to refer to the object itself
                  return amount/100;
              }
      };
      return me;
}

没有理由使用new关键字:此“构造函数”创建的对象是一个对象文字,它只有一个原型(Object.prototype ,并没有明确的构造函数名称)。添加new将使this指向函数本身中的新对象,但由于您没有使用它,并且您的函数返回另一个对象,因此该对象永远不会返回。

严格来说:一些JS引擎会为每个新实例创建新的函数对象。一些现代对象优化了这一点,实际上只创建了一个函数对象。为了安全起见,您可以围绕事物包装另一个闭包,以确保只有一个函数,无论引擎运行什么引擎:

var someObjectGenerator = (function()
{
    var oneOffFunction = function()
    {
        console.log('this function will only be created once');
    }
    return function(amount)
    {
        return {    foo:oneOffFunction,
                    toCents: function()
                    {
                        return amoutn/100;
                    }
                };
    };
};

当然,在具有moneyamount变量的范围之上的范围内创建的函数将无法访问该变量,因此在这种情况下,您必须创建新功能...但JS对象非常便宜,所以不要担心它 。再次,大多数引擎都很好地解决了这个问题。