我从AngularJS
开始,阅读开发者指南 - 概念性概述。
我只得到了第一个例子 - 简单的计算器 - 并发现了一个奇怪的AngularJS行为,即以下表达式:
{{qty * cost |货币}}
小数字可以工作,大数字会在结果中输出指数,但是在结果之间有一些值,意外地是NaN
。
当然,香草javascript中的相同数字不会返回NaN
。
例如:
2222222222 and 200000000000
有人可以解释一下这种行为吗?
答案 0 :(得分:1)
解决这些问题的最佳方法是尝试重新创建它们。开发人员工具中的控制台非常适合这种情况。
> 2222222222 * 200000000000
444444444400000000000
所以,这里没什么奇怪的。 Javascript可以处理乘法。这意味着我们可以确定奇怪的是从货币过滤器到达。
下一站是过滤器本身的来源。如果您访问Angular module的文档,那么会有一个view source按钮,可以直接转到GitHub上的源文件。
货币过滤器本身并没有太多代码。
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(amount, currencySymbol, fractionSize) {
if (isUndefined(currencySymbol)) {
currencySymbol = formats.CURRENCY_SYM;
}
if (isUndefined(fractionSize)) {
fractionSize = formats.PATTERNS[1].maxFrac;
}
// if null or undefined pass it through
return (amount == null)
? amount
: formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
replace(/\u00A4/g, currencySymbol);
};
}
唯一使用amount
变量的地方是formatNumber
调用,还有来自$locale.NUMBER_FORMATS
的其他内容。
快速查看src/ngLocale/angular-locale_en-us.js
向我们展示了我们关注的内容。
formats.PATTERNS[1] = {
"gSize": 3,
"lgSize": 3,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "-\u00a4",
"negSuf": "",
"posPre": "\u00a4",
"posSuf": ""
}
其他参数只是格式化细节。现在让我们来看看这个formatNumber
方法。这很长,所以我会尝试切入重要的部分。
在早期,我们的号码将转换为字符串var numStr = number + '',
。
在此之后通过读取来干燥运行代码变得非常重要,所以回到演示并在formatNumber
函数的开头抛出一个断点(在非缩小版本中的第18580行)角)。
如果我们跳过功能,我们可以在本地范围内看到numStr
分配给h
。当我们步入第18627行时,h
本地变为"NaN"
。
在18617,我可以看到两个似乎包含我们价值的变量b
和h
。
b = 444444444400000000000
h = "444444444400000000000"
在18619 b
成为NaN
。 h
保持不变。所以违规的代码行是
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
现在我们到了某个地方。这段代码中充斥着试图将数字强制转换为字符串的内容,反之亦然。
fractionSize
作为该函数的最后一个参数进入。我们之前知道的是我们的区域设置编号格式模式的maxFrac
字段。
"maxFrac": 2,
让我们开始替换函数调用中的值,以便我们可以在控制台中运行它。
var number = 444444444400000000000;
var fractionSize = 2;
+(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
我们可以使用它来确认这确实会创建NaN
。
现在要分解它以找出原因。最里面的表达式如下
number.toString() + 'e' + fractionSize
// "444444444400000000000e2"
使用+
运算符立即将此值强制转换为数字。
+(number.toString() + 'e' + fractionSize)
// 4.444444444e+22
然后这个数字是四舍五入的,但它保持不变。然后它被转换回一个字符串。
Math.round(+(number.toString() + 'e' + fractionSize)).toString()
// "4.444444444e+22"
为了能够阅读此内容,我们可以调用该值x
。
x = "4.444444444e+22"
现在我们将x
与另一个'e'
和-fractionSize
连接起来。
x + 'e' + -fractionSize
// "4.444444444e+22e-2"
我们开始看到这出错了。最后,再次使用+
运算符将字符串强制转换为数字。
+(x + 'e' + -fractionSize)
// NaN
宾果
发生什么事了?数字四舍五入的实现似乎有一个优势。之前它会检查该数字是否包含指数,然后相应处理它。但是,如此处所示,存在一些边缘情况,其中大数字在此舍入方法中以两个指数结束。看作是无效的符号,数字不能被强制,最终为NaN。
这个边缘情况几乎肯定与你选择乘以的数字相关的事实,超过了Number.MAX_SAFE_INTEGER
(9007199254740991)的界限。一般来说,当您使用这么大的数字时,您可能会发现许多代码无法以您预期的方式处理它们。特别是如果该代码将在字符串和数字之间进行前后转换。
最后,该NaN值将转换回字符串并送入货币过滤器。