【问题标题】:Details on returning an object literal from a closure in JavaScript从 JavaScript 中的闭包返回对象字面量的详细信息
【发布时间】:2012-10-29 21:34:34
【问题描述】:

背景:我想重写一个库(我没有写)以避免闭包编译器使用高级选项生成警告。根据这个问题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:为什么在调用 toCentscurrentValue 没有更新? (我猜这是因为 currentValue 是一个文字(?),它在 CurrencyObject 首次执行时被初始化。如果是这种情况,那么返回属性 currentValue?)

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

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

TIA

顺便说一句:如果有人想知道,对象文字中的符号会被引用以避免闭包编译器重命名它们。

【问题讨论】:

  • 就像一个注释 - makeDollars 在你的小提琴中不是一个有效的调用
  • @ianpgall 感谢您的提醒。固定。

标签: javascript closures google-closure-compiler


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

    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关键字:这个"constructor"创建的对象是一个对象字面量,它只有1个原型(Object.prototype,并且没有显式构造函数名称)。添加new 将使this 指向函数本身中的一个新对象,但是由于您没有使用它,并且您的函数返回另一个对象,因此该对象永远不会返回。

    严格来说:一些 JS 引擎会为每个新实例创建新的函数对象。一些现代对象对此进行了优化,实际上只会创建 1 个函数对象。为了安全起见,您可以在事物周围包裹另一个闭包,以确保只有 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;
                        }
                    };
        };
    };
    

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

    【讨论】:

    • 感谢您的回答。很明显,虽然我还有一些细节需要消化。 (一个多星期没电后,我才在新泽西州赶上来。)
    • 顺便说一句,您在分支 fiddled 中使用 this 的方式不会导致 Closure Compiler 生成警告。 (高级选项可节省 53% 的原始尺寸。)
    • @Karl:乐于助人。我认为闭包编译接受this的使用,因为它不用于set属性,当我使用它访问方法时,我确保this具有我的方法之后(this.toDollars ? this.toDollars() : 1)。希望桑迪之后那里一切都好
    • 共享方法的val 参数将始终为undefined。对?那么为什么val = val || (this.toDollars ? this.toDollars() : 1);。我不明白||val 的原因。也就是说,为什么不只是val = (this.toDollars ? this.toDollars() : 1);? TIA。
    • @Karl,我添加了 val 参数以防您想将共享函数用作设置器,或者如果您(意外或故意)在其他地方分配对该函数的引用。
    猜你喜欢
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-26
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多