6

背景:我想重写一个库(我没有写)以避免闭包编译器使用高级选项生成警告。根据这个问题JavaScript “this”关键字和闭包编译器警告,答案是使用闭包重写代码。目的是避免使用关键字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:这种语法(with newvar c1 = new CurrencyObject(1.99);不会以我可以检测到的方式改变代码的行为,但我认为存在差异。它是什么?

Q3:当c2被实例化时,是创建函数的副本还是c1c2共享相同的(函数)代码?(如果正在创建函数的副本,我应该进行哪些更改以避免这种情况?)

TIA

顺便说一句:如果有人想知道,对象文字中的符号会被引用以避免让 Closure-Compiler 重命名它们。

4

2 回答 2

3

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

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

或者,您可以摆脱money闭包变量而只使用该currentValue属性。这将要求您使用thistoCentsandtoDollars方法中获取该属性。在我看来,更简洁的方法是后者(this用于引用对象自己的属性)。我不知道为什么关闭不希望你这样做。

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

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

于 2012-10-29T21:45:16.057 回答
1

更新:
分支了你的小提琴,并添加了所有实例实际上将共享的几个方法(即:不会为每个实例创建新的函数对象):例如,将金额转换为欧元或英镑的方法。我也省略了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关键字:由这个“构造函数”创建的对象是一个对象字面量,它只有 1 个原型(Object.prototype,并且没有明确的构造函数名称)。添加newthis指向函数本身中的一个新对象,但由于您没有使用它,并且您的函数返回另一个对象,因此该对象永远不会返回。

严格来说:一些 JS 引擎会为每个新实例创建新的函数对象。一些现代对象对此进行了优化,实际上只会创建 1 个函数对象。为了安全起见,您可以在事物周围包装另一个闭包,以确保只有一个函数,而不管运行您的代码的引擎是什么:

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;
                    }
                };
    };
};

当然,在具有moneyoramount变量的作用域之上创建的函数将无法访问该变量,因此在这种情况下,您必须创建新函数......但是 JS 对象非常便宜,所以不要太担心
同样,大多数引擎都很好地处理了这个问题。

于 2012-10-29T21:51:22.190 回答