Статьи

Что такое {} + {} в JavaScript?

Недавно во время молниеносной беседы Гэри Бернхардта « Wat » была отмечена интересная особенность JavaScript: вы получаете неожиданные результаты при добавлении объектов и / или массивов. Этот пост объясняет эти результаты.

Общее правило добавления в JavaScript простое: вы можете добавлять только числа и строки, все остальные значения будут преобразованы в один из этих типов. Чтобы понять, как работает это преобразование, нам сначала нужно понять несколько других вещей. Всякий раз, когда упоминается параграф (например, §9.1), он ссылается на языковой стандарт ECMA-262 (ECMAScript 5.1).

Давайте начнем с быстрого освежения. В JavaScript есть два вида значений: примитивы и объекты [1] . Примитивные значения: undefined, null, логические значения, числа и строки. Все остальные значения являются объектами, включая массивы и функции.

Преобразование значений

Оператор плюс выполняет три вида преобразования: он преобразует значения в примитивы, числа и строки.

Преобразование значений в примитивы с помощью ToPrimitive ()

Внутренняя операция ToPrimitive () имеет следующую подпись:

    ToPrimitive(input, PreferredType?)

Необязательный параметр PreferredType — это Number или String. Это только выражает предпочтение, результатом всегда может быть любое примитивное значение. Если PreferredType равен Number, выполняются следующие шаги для преобразования введенного значения (§9.1):

  1. Если вход является примитивным, вернуть его как есть.
  2. В противном случае вход является объектом. Вызовите obj.valueOf (). Если результат примитивный, верните его.
  3. В противном случае вызовите obj.toString (). Если результат является примитивным, верните его.
  4. В противном случае выведите ошибку TypeError.

Если PreferredType — String, шаги 2 и 3 меняются местами. Если PreferredType отсутствует, то он устанавливается на String для экземпляров Date и на Number для всех других значений.

Преобразование значений в числа с помощью ToNumber ()

В следующей таблице объясняется, как ToNumber () преобразует примитивы в число (§9.3).

аргументация Результат
не определено NaN
ноль +0
логическое значение true конвертируется в 1, false конвертируется в +0
числовое значение нет необходимости в преобразовании
строковое значение разобрать номер в строке. Например, «324» преобразуется в 324

Объект obj преобразуется в число путем вызова ToPrimitive (obj, Number) и последующего применения ToNumber () к результату (примитиву).

Преобразование значений в строки с помощью ToString ()

В следующей таблице объясняется, как ToString () преобразует примитивы в строку (§9.8).

аргументация Результат
не определено «Неопределенный»
ноль «ноль»
логическое значение либо «правда», либо «ложь»
числовое значение число в виде строки, например «1.765»
строковое значение нет необходимости в преобразовании

Объект obj преобразуется в число, вызывая ToPrimitive (obj, String) и затем применяя ToString () к результату (примитиву).

Пробовать

Следующий объект позволяет наблюдать за процессом конвертации.

    var obj = {
        valueOf: function () {
            console.log("valueOf");
            return {}; // not a primitive
        },
        toString: function () {
            console.log("toString");
            return {}; // not a primitive
        }
    }

Число, вызываемое как функция (в отличие от конструктора), внутренне вызывает ToNumber ():

    > Number(obj)
    valueOf
    toString
    TypeError: Cannot convert object to primitive value

прибавление

Учитывая следующее дополнение.

    value1 + value2

Чтобы оценить это выражение, предпринимаются следующие шаги (§11.6.1):

  1. Преобразуйте оба операнда в примитивы (математическая запись, а не JavaScript):
        prim1 := ToPrimitive(value1)
        prim2 := ToPrimitive(value2)
    

    PreferredType опущен и, таким образом, Number для не-дат, String для дат.

  2. Если либо prim1, либо prim2 является строкой, преобразуйте оба в строки и верните объединение результатов
  3. В противном случае, преобразуйте оба числа prim1 и prim2 в числа и верните сумму результатов.

Ожидаемые результаты

Когда вы добавляете два массива, все работает как положено:

    > [] + []
    ''

Преобразование [] в примитив сначала пытается использовать функцию valueOf (), которая возвращает сам массив (this):

    > var arr = [];
    > arr.valueOf() === arr
    true

Так как этот результат не является примитивом, toString () вызывается следующим и возвращает пустую строку (которая является примитивом). Следовательно, результатом [] + [] является объединение двух пустых строк.

Добавление массива и объекта также соответствует нашим ожиданиям:

    > [] + {}
    '[object Object]'

Объяснение: преобразование пустого объекта в строку приводит к следующему результату.

    > String({})
    '[object Object]'

Предыдущий результат, таким образом, создается путем объединения «» и «[объект объекта]».

Еще примеры, где объекты преобразуются в примитивы:

    > 5 + new Number(7)
    12
    > 6 + { valueOf: function () { return 2 } }
    8
    > "abc" + { toString: function () { return "def" } }
    'abcdef'

Неожиданные результаты

Ситуация становится странной, если первый операнд + является пустым литералом объекта (результаты, как видно на консоли Firefox):

    > {} + {}
    NaN

Что здесь происходит? Проблема в том, что JavaScript интерпретирует первый {} как пустой блок кода и игнорирует его. Поэтому NaN вычисляется путем вычисления + {} (плюс плюс второй {}). Плюс, который вы видите здесь, это не бинарный оператор сложения, а унарный префиксный оператор, который преобразует свой операнд в число таким же образом, как Number (). Например:

    > +"3.65"
    3.65

Следующие выражения все эквивалентны:

    +{}
    Number({})
    Number({}.toString())  // {}.valueOf() isn’t primitive
    Number("[object Object]")
    NaN

Почему первый {} интерпретируется как блок кода? Поскольку полный ввод анализируется как оператор, а фигурные скобки в начале оператора интерпретируются как начало блока кода. Следовательно, вы можете исправить ситуацию, заставив входные данные быть проанализированы как выражение:

    > ({} + {})
    '[object Object][object Object]'

Аргументы функций или методов также всегда анализируются как выражения:

    > console.log({} + {})
    [object Object][object Object]

После предыдущих объяснений вы больше не должны удивляться следующему результату:

    > {} + []
    0

Опять же, это интерпретируется как блок кода, за которым следует + []. Следующие выражения эквивалентны:

    +[]
    Number([])
    Number([].toString())  // {}.valueOf() isn’t primitive
    Number("")
    0

Интересно, что REPL Node.js анализирует свои входные данные иначе, чем Firefox или Chrome (который даже использует тот же движок V8 JavaScript, что и Node.js). Следующий вход анализируется как выражение, и результаты менее удивительны:

    > {} + {}
    '[object Object][object Object]'
    > {} + []
    '[object Object]'

Это имеет то преимущество, что больше похоже на результаты, которые вы получаете, используя входные данные в качестве аргументов console.log (). Но это также не похоже на использование ввода в качестве операторов в программах.

Что все это значит?

В большинстве случаев не так сложно понять, как + работает в JavaScript: вы можете добавлять только цифры или строки. Объекты преобразуются либо в строку (если другой операнд является строкой), либо в число (в противном случае). Если вы хотите объединить массивы, вам нужно использовать метод:

    > [1, 2].concat([3, 4])
    [ 1, 2, 3, 4 ]

В JavaScript нет встроенного способа «объединять» (объединять) объекты. Вам нужно использовать библиотеку, такую ​​как
Underscore :

    > var o1 = {eeny:1, meeny:2};
    > var o2 = {miny:3, moe: 4};
    > _.extend(o1, o2)
    { eeny: 1,
      meeny: 2,
      miny: 3,
      moe: 4 }

Примечание. В отличие от Array.prototype.concat (), extension () изменяет свой первый аргумент:

    > o1
    { eeny: 1,
      meeny: 2,
      miny: 3,
      moe: 4 }
    > o2
    { miny: 3, moe: 4 }

Если вы хотите повеселиться с операторами, вы можете прочитать пост «
Перегрузка поддельных операторов в JavaScript ».

Рекомендации

  1. Значения JavaScript: не все является объектом

 

С http://www.2ality.com/2012/01/object-plus-object.html