В статье «Wat» для 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 + Object
{} + {}
result:
[Object][Object]
Это также не соответствует видео, и как вывод переменной приводит к двум объектам? Может быть, мой JSFiddle не так.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Ват + 1 приводит к wat1wat1wat1wat1
...
Я подозреваю, что это просто прямое поведение, которое при попытке вычесть число из строки приводит к NaN.
источник
Array(16).join("wat" - 1) + " Batman!"
{} + {}
.Ответы:
Вот список объяснений результатов, которые вы видите (и должны видеть). Ссылки, которые я использую, взяты из стандарта ECMA-262 .
[] + []
При использовании оператора сложения и левый, и правый операнды сначала преобразуются в примитивы ( §11.6.1 ). Согласно §9.1 , преобразование объекта (в данном случае массива) в примитив возвращает его значение по умолчанию, которое для объектов с допустимым
toString()
методом является результатом вызоваobject.toString()
( §8.12.8 ). Для массивов это то же самое, что вызовarray.join()
( §15.4.4.2 ). Присоединение к пустому массиву приводит к пустой строке, поэтому шаг # 7 оператора сложения возвращает конкатенацию двух пустых строк, которая является пустой строкой.[] + {}
Аналогично
[] + []
, оба операнда сначала преобразуются в примитивы. Для «Объектных объектов» (§15.2) это снова является результатом вызоваobject.toString()
, который для ненулевых, неопределенных объектов равен"[object Object]"
( §15.2.4.2 ).{} + []
Здесь
{}
здесь не анализируется как объект, но вместо этого, как пустой блок ( §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
в результате:Array(16).join("wat" - 1)
В соответствии с §15.4.1.1 и §15.4.2.2 ,
Array(16)
создается новый массив длиной 16. Чтобы получить значение аргумента для соединения, шаги № 5 и № 6 §11.6.2 показывают, что мы должны преобразовать оба операнда в номер с помощью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 ):Следуя шагу 10 §15.4.4.5 , мы получаем 15 повторений конкатенации
"NaN"
и пустой строки, что равно результату, который вы видите. При использовании"wat" + 1
вместо"wat" - 1
аргумента оператор сложения преобразует1
в строку вместо преобразования"wat"
в число, поэтому он эффективно вызываетArray(16).join("wat1")
.Относительно того, почему вы видите разные результаты для
{} + []
случая: при использовании его в качестве аргумента функции вы заставляете оператор быть ExpressionStatement , что делает невозможным его анализ{}
как пустой блок, поэтому вместо этого он анализируется как пустой объект буквальный.источник
[]+1
значительной степени следует той же логике[]+[]
, что1.toString()
и операнд rhs. Для[]-1
видеть объяснение"wat"-1
в пункте 5. Помните , чтоToNumber(ToPrimitive([]))
есть 0 (точка 3).Это скорее комментарий, чем ответ, но по какой-то причине я не могу прокомментировать ваш вопрос. Я хотел исправить твой код JSFiddle. Однако я разместил это в Hacker News, и кто-то предложил мне опубликовать это здесь.
Проблема в коде JSFiddle заключается в том, что
({})
(открывающие скобки внутри скобок) - это не то же самое, что{}
(открывающие скобки как начало строки кода). Поэтому, когда вы печатаете,out({} + [])
вы заставляете{}
быть чем-то таким, чего нет при вводе{} + []
. Это является частью общего 'javascript'.Основная идея была в простом JavaScript, который хотел разрешить обе эти формы:
Для этого были сделаны две интерпретации открывающей скобки: 1. она не требуется и 2. она может появиться где угодно .
Это был неправильный ход. Реальный код не имеет открывающей скобки, появляющейся посреди ниоткуда, и реальный код также имеет тенденцию быть более хрупким, когда он использует первую форму, а не вторую. (Примерно раз в месяц на моей последней работе меня вызывали на стол коллег, когда их модификации в моем коде не работали, и проблема заключалась в том, что они добавляли строку в «если», не добавляя фигурные фигурные скобки. В конце концов я просто принял привычку, что фигурные скобки всегда требуются, даже если вы пишете только одну строку.)
К счастью, во многих случаях eval () будет копировать всю поддержку JavaScript. Код JSFiddle должен читать:
[Также это первый раз, когда я пишу document.writeln за много-много лет, и я чувствую себя немного грязно, когда пишу что-либо, связанное с document.writeln () и eval ().]
источник
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere
- Я не согласен (вроде): У меня часто в прошлом используемых блоков , как это размах переменных в C . Эта привычка была поднята некоторое время назад при выполнении встроенного C, где переменные в стеке занимают место, поэтому, если они больше не нужны, мы хотим освободить пространство в конце блока. Тем не менее, ECMAScript только в пределах блоков function () {}. Итак, хотя я не согласен с тем, что концепция неверна, я согласен с тем, что реализация в JS ( возможно ) неверна.let
для объявления переменных области блока.Я второе решение @ Ventero. Если вы хотите, вы можете более подробно узнать, как
+
преобразуются его операнды.Первый шаг (§9.1): преобразовать оба операнда в примитивы (значениями примитивов являются
undefined
,null
логические, числа, строки; все другие значения являются объектами, включая массивы и функции). Если операнд уже примитивен, все готово. Если нет, то это объект,obj
и выполняются следующие шаги:obj.valueOf()
. Если он возвращает примитив, все готово. Прямые экземплярыObject
и массивы возвращают себя, так что вы еще не закончили.obj.toString()
. Если он возвращает примитив, все готово.{}
и[]
оба возвращают строку, так что все готово.TypeError
.Для дат шаги 1 и 2 меняются местами. Вы можете наблюдать поведение конвертации следующим образом:
Взаимодействие (
Number()
сначала преобразуется в примитив, затем в число):Второй шаг (§11.6.1): если один из операндов является строкой, другой операнд также преобразуется в строку, и результат получается путем объединения двух строк. В противном случае оба операнда преобразуются в числа, и результат получается путем их добавления.
Более подробное объяснение процесса преобразования: « Что такое {} + {} в JavaScript? »
источник
Мы можем ссылаться на спецификацию, и это здорово и наиболее точно, но большинство случаев также можно объяснить более понятным способом с помощью следующих утверждений:
+
и-
операторы работают только с примитивными значениями. Более конкретно+
(сложение) работает со строками или числами, а также+
(унарные) и-
(вычитание и унарное) работает только с числами.valueOf
илиtoString
, которые доступны на любом объекте. Вот почему такие функции или операторы не выдают ошибки при вызове объектов.Таким образом, мы можем сказать, что:
[] + []
такой же как иString([]) + String([])
такой же как'' + ''
. Я упоминал выше, что+
(сложение) также допустимо для чисел, но в JavaScript нет действительного числового представления массива, поэтому вместо него используется добавление строк.[] + {}
такой же как иString([]) + String({})
такой же как'' + '[object Object]'
{} + []
, Этот заслуживает большего объяснения (см. Ответ Ventero). В этом случае фигурные скобки рассматриваются не как объект, а как пустой блок, поэтому он оказывается таким же, как+[]
. Унарный+
работает только с числами, поэтому реализация пытается получить число из[]
. Сначала он пытается,valueOf
который в случае массивов возвращает тот же объект, а затем пытается последнее средство: преобразованиеtoString
результата в число. Мы можем написать это как то+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):
Таким образом, с различными типами ввода, возможности вывода увеличиваются.
Алгоритм сложения
Примитивами JavaScript являются string, number, null, undefined и boolean (Symbol скоро появится в ES6). Любое другое значение является объектом (например, массивами, функциями и объектами). Процесс принуждения для преобразования объектов в примитивные значения описывается так:
Если примитивное значение возвращается, когда вызывается object.valueOf (), вернуть это значение, в противном случае продолжить
Если примитивное значение возвращается, когда вызывается object.toString (), вернуть это значение, в противном случае продолжить
Бросить ошибку типа
Примечание: Для значений даты порядок должен вызывать toString перед valueOf.
Если любое значение операнда является строкой, выполните конкатенацию строк
В противном случае преобразуйте оба операнда в их числовое значение, а затем добавьте эти значения.
Знание различных значений приведения типов в JavaScript помогает прояснить запутанные результаты. Смотрите таблицу принуждения ниже
Также полезно знать, что JavaScript-оператор JavaScript является левоассоциативным, поскольку это определяет, какие выходные данные будут иметь случаи, включающие более одной + операции.
Усиливая таким образом 1 + «2» даст «12», потому что любое добавление, включающее строку, всегда будет по умолчанию для конкатенации строк.
Вы можете прочитать больше примеров в этом сообщении в блоге (отказ от ответственности, я написал это).
источник