为什么八进制字面量作为字符串不能转换成数字?

23

在JavaScript中,为什么八进制数字符串被转换成十进制数?我可以使用 Number()+ 将十六进制字面量字符串转换成数字,为什么不能将八进制转换成数字呢?

例如:

1000 === +"1000" // -> true
0xFF === +"0xFF" // -> true
0100 === +"0100" // -> false - +"0100" gives 100, not 64

我知道可以使用 parseInt("0100" [, 8]) 进行解析,但是我想知道为什么强制转换不像十六进制和十进制数字那样有效。

另外,有人知道为什么在ECMAScript第5版严格模式下会删除八进制字面量吗?

3个回答

24
我有点晚回答这个问题,但我认为我能给出一个好的答案。 被接受的答案并没有告诉你比你在问题中已经知道的更多,它提到:Number(value)作为+value工作,但不作为parseInt(value)。 关键是要知道在类型转换和解析之间存在语义差异。 因为Number构造函数作为函数调用Number(value))和一元操作符 + +value)在幕后使用ToNumber内部操作。这些结构的目的是进行类型转换。 当ToNumber应用于字符串类型时,使用特殊的语法产生,称为StringNumericLiteral。 此产生仅可容纳十进制文字和十六进制整数文字:
StrNumericLiteral :::
   StrDecimalLiteral
   HexIntegerLiteral

这个语法与“普通”NumericLiterals的语法之间也存在语义差异。

StringNumericLiteral

  • 可以在前面和/或后面带有空格和/或行终止符。
  • 十进制数可以有任意数量的前导0位。 没有八进制!
  • 十进制数可以在前面加上+或-表示其符号。
  • 空字符串或只包含空格的字符串会转换为+0。

现在我将使用parseIntparseFloat函数。

这些函数的目的显然是进行解析,这在语义上与类型转换不同,例如:

parseInt("20px");     // 20
parseInt("10100", 2); // 20
parseFloat("3.5GB");  // 3.5
// etc..

值得一提的是,parseInt的算法在ECMAScript第5版规范中发生了改变,它不再仅因为有前导零而将数字基数解释为八进制数:
parseInt("010"); // 10, ECMAScript 5 behavior
parseInt("010"); // 8,  ECMAScript 3 behavior

正如您所看到的,这引入了ES3和ES5实现之间行为上的不兼容性,因此建议始终使用基数参数以避免任何可能的问题。

现在是您的第二个问题:

为什么ECMAScript第五版在严格模式下删除了八进制字面量?

实际上,这个去除八进制字面量的努力可以追溯到1999年。自ECMAScript第三版规范以来,八进制字面量产生(OctalIntegerLiteralOctalEscapeSequence)已从NumericLiteral的语法中删除,它们可能会因向后兼容也在ES5中)旧版本的标准而被包括在内。

事实上,它们包含在所有主要的实现中,但从技术上讲,一个符合ES3或ES5的实现可以选择不包括它们,因为它们被描述为非规范性的

这是第一步,现在ECMAScript 5 严格模式完全禁止它们。

但是为什么?

因为八进制字面量被认为是一个容易出错的特性,实际上,在过去它们曾经引起了不经意或难以捕捉的错误,就像parseInt的隐式八进制问题一样。
现在,在严格模式下,八进制字面量将会导致SyntaxError异常——目前只能在Firefox 4.0 Beta中观察到。

1
这是一个很好的答案,超出了我的最初期望。我想我在规范中忽略了StringNumericLiteral,而且我肯定不知道允许使用空格。那只是其中一件事情,我总是期望空格会导致NaN - Andy E
2
谢谢 @Andy,是的,我经常看到人们对 isNaN("\t\r\n ") 返回 false 感到惊讶 ;) - Christian C. Salvadó

4

因为实际上你并没有进行适当意义上的转换(JS没有转换)-它只是类型转换。

当你在Javascript中有任何字面量并对其执行方法时,会在幕后为你创建一个对象。

例如"foo".toUpperCase(),被替换为大致看起来像这样的代码评估:new String( "foo" ).toUpperCase();

由于字符串不能使用一元+运算符进行评估,JS将您的字符串转换为数字 - 它不使用parseInt()parseFloat()内部 - 你猜对了 - 它使用Number()

所以,你看到的值就是从Number()返回的值,它似乎不假定八进制。


谢谢Peter,我已经假定在一元“转换”时使用Number()(https://dev59.com/AnVD5IYBdhLWcg3wL4cA#2243631),但是对我来说,`Number()`不接受由语法定义的任何字符串化数字文字似乎很奇怪。它似乎更有意义地重用已经存在于解析数字文字的幕后代码。感谢您提供有关幕后对象创建的信息,我以前读过这个并且忘记了,当它们为您自动完成时,很容易忘记这些事情 :-) - Andy E
当我的答案未被采纳时,我总是感到不爽,因为SE系统没有告诉你哪个是正确的。所以我想礼貌地告诉你你失去了15分的原因。CMS写了一个很好的回答,更详细地解释了原因,所以接受他的答案似乎是合适的。抱歉,并感谢你的回答 :-) - Andy E
@Andy 不用担心 - 我同意 - 他的回答更好。干杯。 - Peter Bailey

2
为了详细解释为什么在ES5中删除了八进制支持,这主要是因为对于初学者或非程序员来说,语法是意外的。 想象一下竖直排列一系列数字(可能正在相加),使用前导零来对齐它们,例如 - 如果您的数字不使用8或9,则它们最终将被视为八进制。 突然间,你的代码就无法理解了! 这就是为什么删除了八进制支持。 如果有一个不会造成这种不幸的不同八进制语法,它可能会在某个时候添加,但现在八进制已经被删除了。
关于过去回答中提到的不兼容的parseInt更改:没有实现进行了此更改,我认为也没有实现将进行此更改。 ES5基本上是现实的。 它的新功能通常不会破坏现有代码(除了当然必须小心地使用新功能以避免作为该使用的一部分破坏现有代码的新代码之外)。 它的不兼容性大多可以忽略不计,或者由于现实世界的实现出于兼容性原因而漠视了规范而变得无关紧要。 但并不是所有的不兼容都是有根据的:有些更多的是理想化而不是协调。 更改parseInt是一个理想化更改的例子。 它会破坏现有代码,该代码期望八进制语法(没有显式基数)解析为八进制。
在几天的时间内,SpiderMonkey(Mozilla的JavaScript引擎)实现了一种半路更改,使得在从严格模式代码调用parseInt时,忽略八进制,并在从非严格模式代码调用时支持八进制。 这更接近于ES5所希望的,但它对于将非严格代码转换为严格模式是一个明显的阻碍,可能会让用户感到困惑,而且 - 也许最有趣的是对于实施者来说 - 这意味着你不能在JavaScript本身中实现parseInt(因为规范中没有检查调用函数的严格性的方法),这可能在将来某个时候是可取的(以减少攻击面,简化实现等)。 因此,我们撤销了该依赖项。(我编写了使parseInt调用程序相关的补丁,并审核了撤消它的补丁,这是在我的初始补丁着陆后进一步讨论产生的。)parseInt现在再次符合ES3,并且考虑到网络的现状以及ES5的语义可能与之不兼容,我怀疑我们会进行更改。 因此,我怀疑其他人也不会进行更改。(我也相当确定他们会同意我们对于Web与ES5的理想化禁止parseInt中隐式八进制语法的程度的不兼容性的估计,以及我们的其他原因。即使我们要进行更改,我也不确定他们会跟随,我认为他们聪明地不会这样做。)

+1,谢谢您提供额外的见解。我同意,八进制字面量的语法对于不知情的人来说相当危险,而十六进制字面量语法的“0x”前缀使它与十进制字面量有所区别。 - Andy E

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接