背景:我想重写一个库(我没写过),以避免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 :此语法(使用new
)var c1 = new CurrencyObject(1.99);
不会以我能检测到的方式更改代码的行为,但我认为存在差异。它是什么?
Q3 :实例化 c2 时,是正在创建的功能的副本,还是 c1 和 c2 共享相同的(功能)代码? (如果正在创建函数的副本,我会做出哪些更改以避免这种情况?)
TIA
BTW:如果有人想知道,引用对象文字中的符号是为了避免让Closure-Compiler重命名它们。
答案 0 :(得分:3)
Q1:您创建了两个包含Amount
单独副本的变量。一个是在闭包中捕获的money
变量中。另一个副本位于返回对象的currentValue
属性中。这些是彼此没有关联的单独变量,而currentValue
的初始值是使用money
变量的值初始化的。
要解决此问题,您必须确定只有一个地方存储您的currentValue
并在那里引用它。您只能在闭包中使用money
局部变量,但这需要将currentValue
属性更改为getter函数(检索money
值)而不是直接数据属性
或者,您可以删除money
闭包变量并仅使用currentValue
属性。这需要您使用this
在toCents
和toDollars
方法中获取该属性。在我看来,更简洁的方法是后者(使用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;
}
};
};
};
当然,在具有money
或amount
变量的范围之上的范围内创建的函数将无法访问该变量,因此在这种情况下,您必须创建新功能...但JS对象非常便宜,所以不要担心它 。再次,大多数引擎都很好地解决了这个问题。