Почему восьмеричный литерал в виде строки не преобразуется в число?

Почему в JavaScript строка восьмеричного числа преобразуется в десятичное число? Я могу преобразовать шестнадцатеричную литеральную строку, используя Number() или +, почему бы не использовать восьмеричную?

Например:

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

Я знаю, что могу анализировать с помощью parseInt("0100" [, 8]), но я хотел бы знать, почему приведение не работает, как с шестнадцатеричными и десятичными числами.

Кроме того, кто-нибудь знает, почему восьмеричные литералы удаляются из ECMAScript 5th Edition в строгом режиме?


person Andy E    schedule 30.03.2010    source источник


Ответы (3)


Я немного опоздал с вопросом, но я думаю, что могу дать хороший ответ.

Принятый ответ не говорит вам ничего больше, чем то, что вы на самом деле знаете, и упоминает в самом вопросе: Number(value) работает как +value, но не как parseInt(value).

Важно знать, что существует семантическая разница между преобразованием типов и анализом.

Почему строка восьмеричного числа преобразуется в десятичное число?

Поскольку конструктор чисел вызывается как функция (Number(value)) и Унарный + оператор (+value) за кулисами использует ToNumber внутренняя операция. Целью этих конструкций является преобразование типов.

Когда ToNumber применяется к типу String, используется специальная грамматика, называемая StringNumericLiteral.

Это производство может содержать только десятичные литералы и шестнадцатеричные целые литералы:

...

StrNumericLiteral :::
   StrDecimalLiteral
   HexIntegerLiteral

...

Существуют также семантические различия между этой грамматикой и грамматикой «обычного» NumericLiterals.

A StringNumericLiteral:

  • Может предваряться и/или сопровождаться пробелами и/или разделителями строк.
  • То есть десятичное число может иметь любое количество первых 0 цифр. никаких восьмеричных чисел!
  • Перед десятичным числом может стоять + или - для обозначения его знака.
  • То, что пусто или содержит только пробелы, преобразуется в +0.

Теперь я перейду к функциям parseInt и parseFloat.

Целью этих функций, очевидно, является анализ, который семантически отличается от преобразования типов, например:

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

Стоит отметить, что алгоритм parseInt изменился в спецификации ECMAScript 5th Edition. , он больше не интерпретирует систему счисления числа как восьмеричную только из-за наличия ведущего нуля:

parseInt("010"); // 10, ECMAScript 5 behavior
parseInt("010"); // 8,  ECMAScript 3 behavior

Как видите, это привело к несовместимости в поведении между реализациями ES3 и ES5, и как всегда рекомендуется использовать аргумент radix, чтобы избежать возможных проблем.

Теперь ваш второй вопрос:

Почему восьмеричные литералы удаляются из ECMAScript 5th Edition в строгом режиме?

На самом деле, эта попытка избавиться от восьмеричных литералов предпринимается с 1999 года. Восьмеричные литералы (OctalIntegerLiteral и OctalEscapeSequence) были удалены из грамматики NumericLiterals с тех пор, как спецификация ECMAScript 3rd Edition, они могут быть включены для обратная совместимость (также в ES5 ) со старыми версиями стандарта.

На самом деле они включены во все основные реализации, но технически реализация, совместимая с ES3 или ES5, может не включать их, поскольку они описываются как ненормативные.

Это был первый шаг, теперь ECMAScript 5 строгий режим полностью их запрещает.

Но почему?

Поскольку они считались подверженными ошибкам, и на самом деле в прошлом они вызывали непреднамеренные или сложные ошибки — точно так же, как та же проблема неявных восьмеричных чисел parseInt —.

Теперь в строгом режиме восьмеричный литерал вызовет исключение SyntaxError, которое в настоящее время наблюдается только в бета-версиях Firefox 4.0.

person Christian C. Salvadó    schedule 04.10.2010
comment
Это отличный ответ и больше, чем я изначально ожидал. Думаю, я упустил из виду StringNumericLiteral в спецификации и, конечно же, не знал, что пробелы разрешены. Это только одна из тех вещей, я всегда ожидал, что пробел приведет к NaN. - person Andy E; 04.10.2010
comment
Спасибо @Andy, да, я действительно часто вижу людей, удивленных тем, что, например. isNaN("\t\r\n ") возвращает false ;) - person Christian C. Salvadó; 04.10.2010

Потому что на самом деле вы не выполняете приведение в правильном смысле (в JS нет приведения) - это просто жонглирование типами.

Когда у вас есть какой-либо литерал в Javascript и вы применяете к нему метод, за кулисами для вас создается объект.

"foo".toUpperCase() например, заменяется оценкой кода, который примерно выглядит так new String( "foo" ).toUpperCase();

Поскольку строки не могут быть оценены с помощью унарного оператора +, JS преобразует вашу строку в число — и он не использует parseInt() или parseFloat() внутри — как вы уже догадались — он использует Number().

Таким образом, значение, которое вы видите, это то, что вы увидите в результате возврата Number (), который, по-видимому, не предполагает восьмеричных чисел.

person Peter Bailey    schedule 30.03.2010
comment
Спасибо, Питер, я уже предполагал, что Number() будет использоваться при унарном приведении (stackoverflow.com/questions/61088/hidden-features-of-javascript/), мне кажется странным, что Number() не принимает строковый числовой литерал, определенный грамматикой. Просто кажется, что было бы более разумно повторно использовать уже существующий закулисный код для разбора числовых литералов. Спасибо за информацию о создании закулисных объектов, я читал это раньше, и это вылетело из головы, легко забыть эти вещи, когда они волшебным образом сделаны для вас :-) - person Andy E; 31.03.2010
comment
Меня всегда раздражает, когда один из моих ответов не принимается, потому что система SE не говорит вам, какой именно, поэтому я подумал, что буду вежлив и дам вам знать, куда ушли ваши 15 баллов. CMS написал хороший ответ, в котором более подробно объяснялись причины, поэтому принятие его ответа казалось уместным. Извините, и спасибо за ответ :-) - person Andy E; 04.10.2010
comment
@ Энди, не беспокойся - я согласен - у него есть лучший ответ. Ваше здоровье. - person Peter Bailey; 04.10.2010

Чтобы уточнить, почему восьмеричная поддержка была удалена в ES5, это в основном потому, что для новичка или непрограммиста синтаксис является неожиданным. Представьте себе вертикальное выравнивание ряда чисел (возможно, добавляемых), используя, например, начальные нули для их выравнивания — если ваши числа не используют 8 или 9, они в конечном итоге будут рассматриваться как восьмеричные. Внезапно ваш код пропал без всякой видимой причины! Вот почему восьмеричная поддержка была удалена. Возможно, когда-нибудь будет добавлен другой восьмеричный синтаксис, если он не создаст такой беды (кажется, я помню, что видел 0o755 как одну из соломенных идей), но на данный момент восьмеричный отсутствует.

Что касается несовместимого изменения parseInt, отмеченного в прошлых ответах: ни одна реализация не внесла это изменение, и я подозреваю, что никакая реализация его не внесет. ES5 в основном основан на реальности. Его новые функции, как правило, не нарушают существующий код (за исключением того, что новый код, конечно, должен заботиться об использовании новых функций, чтобы не нарушать существующий код как часть этого использования), который не пытается использовать новые функции. Его несовместимости также в основном незначительны, или они не имеют значения, потому что реальные реализации беспечно игнорировали спецификацию из соображений совместимости. Но не все несовместимости обоснованы: некоторые скорее желательны, чем гармонируют. Изменение на parseInt является примером желательного изменения. Он ломает существующий код, который ожидает, что восьмеричный синтаксис без явного основания будет анализироваться как восьмеричный.

В течение нескольких дней SpiderMonkey (движок JavaScript от Mozilla) реализовал промежуточное изменение, чтобы parseInt при вызове из кода строгого режима игнорировать восьмеричное число и поддерживать восьмеричное число, когда оно не вызывается из кода строгого режима. Это ближе к тому, чего хочет ES5, но это явное препятствие для преобразования нестрогого кода в строгий режим, это, вероятно, сбивает с толку пользователя и, что, возможно, наиболее интересно для разработчиков, означает, что вы не можете реализовать parseInt в самом JavaScript (поскольку в спецификации нет способа проверить строгость вызываемой функции), что может быть желательно в будущем (для уменьшения поверхности атаки, упрощения реализации и т. д.). Итак, мы отменили зависимость. (Я написал патч, чтобы сделать parseInt зависимым от вызывающего абонента, и я просмотрел патч, чтобы отменить его, порожденный дальнейшим обсуждением после того, как мой первоначальный патч был выпущен.) parseInt теперь снова соответствует ES3, и учитывая сеть, как она есть, и что ES5 семантика, вероятно, несовместима с ним, я сомневаюсь, что мы будем менять. Поэтому я сомневаюсь, что другие тоже изменятся. (Я также почти уверен, что они согласятся с нашей оценкой степени несовместимости сети с желательным запретом ES5 неявного восьмеричного синтаксиса в parseInt и, возможно, с другими нашими причинами. не уверен, что они последуют, и я подозреваю, что они поступили бы разумно, если бы этого не сделали.)

person Jeff Walden    schedule 04.10.2010
comment
+1, спасибо за дополнительную информацию. Я согласен, грамматика для восьмеричных литералов довольно опасна для неосведомленных, в отличие от префикса 0x грамматики шестнадцатеричных литералов, который отличает ее от десятичных литералов. - person Andy E; 04.10.2010