Каково объяснение этого странного поведения JavaScript, упомянутого в разговоре " Wat " для CodeMash 2012?
The ' Wat ' talk for CodeMash 2012 в основном указывает на несколько причудливых причуд с Ruby и JavaScript.
Я сделал JSFiddle результатов в http://jsfiddle.net/fe479/9/.
поведение, специфичное для JavaScript (поскольку я не знаю Ruby), перечислены ниже.
я нашел в JSFiddle, что некоторые из моих результатов не соответствуют тем, что в видео, и я не уверен, почему. Я, однако , любопытно узнать, как JavaScript обрабатывает работу за кулисами в каждом случае.
Empty Array + Empty Array
[] + []
result:
<Empty String>
мне очень любопытно + оператор при использовании с массивами в JavaScript.
Это соответствует результату видео.
Empty Array + Object
[] + {}
result:
[Object]
это соответствует результату видео. Что здесь происходит? Почему это объект. Что значит + оператор делать?
Object + Empty Array
{} + []
result
[Object]
это не соответствует видео. Видео предполагает, что результат равен 0, тогда как я получаю [Объект.]
Object + Object
{} + {}
result:
[Object][Object]
это тоже не соответствует видео, и как вывод переменной приводит к двум объектам? Может быть, мой JSFiddle ошибается.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
выполнение wat + 1 приводит к wat1wat1wat1wat1...
Я подозреваю, что это просто простое поведение, которое пытается вычесть число из строки приводит к NaN.
5 ответов:
вот список объяснений результатов, которые вы видите (и должны видеть). Ссылки, которые я использую, из стандарт ECMA-262.
[] + []при использовании оператора сложения левый и правый операнды сначала преобразуются в примитивы (§11.6.1). Согласно §9.1, преобразование объекта (в данном случае массива) в примитив возвращает его значение по умолчанию, которое для объектов с допустимым
toString()метод является результатом вызоваobject.toString()(§8.12.8). Для массивов это то же самое, что вызовarray.join()(§15.4.4.2). Соединение пустого массива приводит к пустой строке, поэтому Шаг #7 оператора сложения возвращает конкатенацию двух пустых строк, которая является пустой строкой.
[] + {}аналогично
[] + [], оба операнда сначала преобразуются в примитивы. за объект объекты " (§15.2), это снова результат вызоваobject.toString(), который для не-null, не определено объектов"[object Object]"(§15.2.4.2).
{} + []The
{}здесь анализируется не как объект, а как пустой блок (§12.1, по крайней мере, пока вы не заставляете это утверждение быть выражением, но об этом позже). Возвращаемое значение пустых блоков является пустым, поэтому результатом этого оператора является то же самое, что+[]. Унарный+оператор (§11.4.6) возвращаетToNumber(ToPrimitive(operand)). Как мы уже знаем,ToPrimitive([])- это пустая строка, и согласно §9.3.1,ToNumber("")равен 0.
{} + {}как и в предыдущем случае, первый
{}анализируется как блок с пустым возвращаемым значением. Опять,+{}это то же самое, чтоToNumber(ToPrimitive({}))иToPrimitive({})и"[object Object]"(см.[] + {}). Чтобы получить результат+{}мы подать заявкуToNumberв строке"[object Object]". При выполнении шагов от §9.3.1, мы получимNaNкак результат:если грамматика не может интерпретировать строку как расширение StringNumericLiteral, то результатом ToNumber и Нэн.
Array(16).join("wat" - 1)по состоянию на §15.4.1.1 и §15.4.2.2,
Array(16)создает новый массив длиной 16. Чтобы получить значение аргумента для соединения,§11.6.2 шаги № 5 и № 6 показывают, что мы должны преобразовать операнды в числа, используяToNumber.ToNumber(1)просто 1 (§9.3), аToNumber("wat")сноваNaNпо состоянию на §9.3.1. Следующий шаг 7 из §11.6.2,§11.6.3 указывает, чтоесли любой из операндов Нэн результат Нэн.
так что аргумент
Array(16).joinиNaN. Следующий §15.4.4.5 (Array.prototype.join), мы должны называтьToStringна аргумент, который является"NaN"(§9.8.1):если m и Нэн, возвратить строку
"NaN".следующий шаг 10 из §15.4.4.5, мы получаем 15 повторений конкатенации
"NaN"и пустой строка, которая равна результату, который вы видите. При использовании"wat" + 1вместо"wat" - 1в качестве аргумента оператор сложения преобразует1в строку вместо преобразования"wat"к номеру, поэтому он эффективно вызываетArray(16).join("wat1").почему вы видите разные результаты для
{} + []case: при использовании его в качестве аргумента функции вы заставляете оператор быть ExpressionStatement, что делает невозможным разобрать{}как пустой блок, поэтому он вместо этого анализируется как пустой литерал объекта.
это скорее комментарий, чем ответ, но по некоторым причинам я не могу прокомментировать ваш вопрос. Я хотел исправить ваш код JSFiddle. Тем не менее, я опубликовал это в Hacker News, и кто-то предложил мне перепечатать его здесь.
проблема в коде JSFiddle заключается в том, что
({})(открытие скобок внутри скобок) не то же самое, что{}(открытие фигурных скобок в начале строки кода). Поэтому, когда вы набираетеout({} + [])вы заставляете{}быть чем-то, чем оно не является при вводе{} + []. Это часть общей " wat " - ности Javascript.основная идея была проста JavaScript хотел разрешить обе эти формы:
if (u) v; if (x) { y; z; }для этого были сделаны две интерпретации открывающей скобки: 1. это не требуется и 2. он может появиться в любом месте.
это был неправильный ход. Реальный код не имеет открывающей скобки, появляющейся в середине нигде, и реальный код также имеет тенденцию быть больше хрупкий, когда он использует первую форму, а не вторую. (Примерно раз в два месяца на моей последней работе меня вызывали на рабочий стол коллеги, когда их изменения в моем коде не работали, и проблема заключалась в том, что они добавили строку в "если" без добавления фигурных скобок. В конце концов я просто принял привычку, что фигурные скобки всегда требуются, даже когда вы пишете только одну строку.)
к счастью, во многих случаях eval () будет реплицировать полную мощность JavaScript. Код JSFiddle должен гласить:
function out(code) { function format(x) { return typeof x === "string" ? JSON.stringify(x) : x; } document.writeln('>>> ' + code); document.writeln(format(eval(code))); } document.writeln("<pre>"); out('[] + []'); out('[] + {}'); out('{} + []'); out('{} + {}'); out('Array(16).join("wat" + 1)'); out('Array(16).join("wat - 1")'); out('Array(16).join("wat" - 1) + " Batman!"'); document.writeln("</pre>");[и это первый раз, когда я написал документ.writeln в течение многих-многих лет, и я чувствую себя немного грязно писать что-либо с участием обоих документов.writeln() и eval().]
Я второе решение @ Ventero. Если вы хотите, вы можете перейти к более подробной информации о том, как
+преобразует его операндов.первый шаг (§9.1): преобразует оба операнда в примитивы (примитивные значения
undefined,null, булевы, числа, строки; все остальные значения являются объектами, включая массивы и функции). Если операнд уже примитивен, вы закончили. Если нет, то это объектobjи следующие шаги выполнено:
- вызов
obj.valueOf(). Если он возвращает примитив, вы сделали. Прямые экземплярыObjectи массивы возвращаются сами, так вы еще не сделали.- вызов
obj.toString(). Если он возвращает примитив, вы сделали.{}и[]как возвращать строку, так что вы сделали.- в противном случае бросить
TypeError.по времени, Шаг 1 и 2 меняются местами. Вы можете наблюдать поведение преобразования как следует:
var obj = { valueOf: function () { console.log("valueOf"); return {}; // not a primitive }, toString: function () { console.log("toString"); return {}; // not a primitive } }взаимодействие (
Number()сначала преобразуется в примитив, а затем в число):> Number(obj) valueOf toString TypeError: Cannot convert object to primitive valueвторой шаг (§11.6.1): если один из операндов является строкой, другой операнд также преобразуется в строку, и результат получается путем объединения двух строк. В противном случае оба операнда преобразуются в числа и результат получается путем их сложения.
более подробное объяснение процесса преобразования: "что {} + {} в JavaScript?"
мы можем ссылаться на спецификацию, и это здорово и наиболее точно, но большинство случаев также можно объяснить более понятным образом с помощью следующих утверждений:
+и-операторы работают только с примитивными значениями. Более конкретно+(дополнение) работает со строками или числами, и+(унарные) и-(вычитание и унарный) работает только с числами.- все собственные функции или операторы, которые ожидайте, что примитивное значение в качестве аргумента сначала преобразует этот аргумент в требуемый примитивный тип. Это делается с
valueOfилиtoString, которые доступны на любом объекте. Вот почему такие функции или операторы не вызывают ошибок при вызове объектов.таким образом, мы можем сказать, что:
[] + []такой же, какString([]) + String([])что же'' + ''. Я уже упоминал выше, что+(сложение) также допустимо для чисел, но не существует допустимого числа представление массива в JavaScript, поэтому вместо него используется добавление строк.[] + {}такой же, какString([]) + String({})что же'' + '[object Object]'{} + []. Это заслуживает большего объяснения (см. ответ Вентеро). В этом случае фигурные скобки рассматриваются не как объект, а как пустой блок, поэтому он оказывается таким же, как+[]. Унарный+работает только с числами, поэтому реализация пытается получить номер[]. Сначала он пытаетсяvalueOfчто в случае массивов возвращает тот же объект, поэтому затем он пытается в крайнем случае: преобразование atoStringрезультат в число. Мы можем написать его как+Number(String([]))что же+Number('')что же+0.Array(16).join("wat" - 1)вычитание-работает только с числами, так как:Array(16).join(Number("wat") - 1), а"wat"не может быть преобразован в допустимое число. Мы получаемNaN, и любая арифметическая операция наNaNрезультатыNaN, так что у нас есть:Array(16).join(NaN).
поддерживать то, что было раньше.
основная причина такого поведения частично связана со слабо типизированной природой JavaScript. Например, выражение 1 + " 2 " неоднозначно, так как существует две возможные интерпретации, основанные на типах операндов (int, string) и (int int):
- пользователь намерен объединить две строки, результат: "12"
- пользователь намерен добавить два числа, результат: 3
таким образом, с разной типы входного сигнала, возможности выхода увеличивают.
алгоритм сложения
- принудить операнды к примитивным значениям
примитивы JavaScript-это строка, число, null, undefined и boolean (символ скоро появится в ES6). Любое другое значение является объектом (например, массивы, функции и объекты). Процесс принуждения для преобразования объектов в примитивные значения описывается следующим образом:
Если примитивное значение возвращается, когда объект.valueOf () вызывается, затем возвращает это значение, в противном случае продолжить
если при object возвращается примитивное значение.вызывается toString (), затем возвращает это значение, в противном случае продолжить
бросьте TypeError
Примечание: для значений даты порядок должен вызывать toString перед valueOf.
Если какое-либо значение операнда является строкой, то выполните строку конкатенация
в противном случае преобразуйте оба операнда в их числовое значение, а затем добавьте эти значения
знание различных значений принуждения типов в JavaScript помогает сделать запутанные выходы более ясными. См. таблицу принуждения ниже
+-----------------+-------------------+---------------+ | Primitive Value | String value | Numeric value | +-----------------+-------------------+---------------+ | null | “null” | 0 | | undefined | “undefined” | NaN | | true | “true” | 1 | | false | “false” | 0 | | 123 | “123” | 123 | | [] | “” | 0 | | {} | “[object Object]” | NaN | +-----------------+-------------------+---------------+также хорошо знать, что оператор JavaScript + является левоассоциативным, поскольку это определяет, какие выходные данные будут включать более одного + операция.
использование Таким образом, 1 + "2" даст "12", потому что любое добавление, включающее строку, всегда будет по умолчанию конкатенацией строк.
вы можете прочитать больше примеров в этот блог (отказ от ответственности я написал его).
Comments