Каково объяснение этого странного поведения 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.

254   5  

5 ответов:

вот список объяснений результатов, которые вы видите (и должны видеть). Ссылки, которые я использую, из стандарт ECMA-262.

  1. [] + []

    при использовании оператора сложения левый и правый операнды сначала преобразуются в примитивы (§11.6.1). Согласно §9.1, преобразование объекта (в данном случае массива) в примитив возвращает его значение по умолчанию, которое для объектов с допустимым toString() метод является результатом вызова object.toString() (§8.12.8). Для массивов это то же самое, что вызов array.join() (§15.4.4.2). Соединение пустого массива приводит к пустой строке, поэтому Шаг #7 оператора сложения возвращает конкатенацию двух пустых строк, которая является пустой строкой.

  2. [] + {}

    аналогично [] + [], оба операнда сначала преобразуются в примитивы. за объект объекты " (§15.2), это снова результат вызова object.toString(), который для не-null, не определено объектов "[object Object]" (§15.2.4.2).

  3. {} + []

    The {} здесь анализируется не как объект, а как пустой блок (§12.1, по крайней мере, пока вы не заставляете это утверждение быть выражением, но об этом позже). Возвращаемое значение пустых блоков является пустым, поэтому результатом этого оператора является то же самое, что +[]. Унарный + оператор (§11.4.6) возвращает ToNumber(ToPrimitive(operand)). Как мы уже знаем, ToPrimitive([]) - это пустая строка, и согласно §9.3.1,ToNumber("") равен 0.

  4. {} + {}

    как и в предыдущем случае, первый {} анализируется как блок с пустым возвращаемым значением. Опять,+{} это то же самое, что ToNumber(ToPrimitive({})) и ToPrimitive({}) и "[object Object]" (см. [] + {}). Чтобы получить результат +{} мы подать заявку ToNumber в строке "[object Object]". При выполнении шагов от §9.3.1, мы получим NaN как результат:

    если грамматика не может интерпретировать строку как расширение StringNumericLiteral, то результатом ToNumber и Нэн.

  5. 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('&gt;&gt;&gt; ' + 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 и следующие шаги выполнено:

  1. вызов obj.valueOf(). Если он возвращает примитив, вы сделали. Прямые экземпляры Object и массивы возвращаются сами, так вы еще не сделали.
  2. вызов obj.toString(). Если он возвращает примитив, вы сделали. {} и [] как возвращать строку, так что вы сделали.
  3. в противном случае бросить 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 что в случае массивов возвращает тот же объект, поэтому затем он пытается в крайнем случае: преобразование a 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):

  • пользователь намерен объединить две строки, результат: "12"
  • пользователь намерен добавить два числа, результат: 3

таким образом, с разной типы входного сигнала, возможности выхода увеличивают.

алгоритм сложения

  1. принудить операнды к примитивным значениям

примитивы JavaScript-это строка, число, null, undefined и boolean (символ скоро появится в ES6). Любое другое значение является объектом (например, массивы, функции и объекты). Процесс принуждения для преобразования объектов в примитивные значения описывается следующим образом:

  • Если примитивное значение возвращается, когда объект.valueOf () вызывается, затем возвращает это значение, в противном случае продолжить

  • если при object возвращается примитивное значение.вызывается toString (), затем возвращает это значение, в противном случае продолжить

  • бросьте TypeError

Примечание: для значений даты порядок должен вызывать toString перед valueOf.

  1. Если какое-либо значение операнда является строкой, то выполните строку конкатенация

  2. в противном случае преобразуйте оба операнда в их числовое значение, а затем добавьте эти значения

знание различных значений принуждения типов в 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

    Ничего не найдено.