08. Object to Primitive

Бывают операции, при которых объект должен быть преобразован в примитив. Например:

  • Преобразование в строковый тип — если объект выводится через alert(obj).

  • Преобразование в численный тип — при арифметических операциях, сравнении с примитивом.

  • Преобразование в логический тип — при if(obj) и других логических операциях.

Основные правила:

  • Любой объект в логическом контексте — true, даже если это пустой массив [] или объект {}.

  • Само преобразование выполняется по алгоритму ToPrimitive.

  • Если на объекте определен символ @@toPrimitive, то вызывается он.

  • Для объекта в строковом контексте вызывается метод toString.

  • Для численного преобразования объекта используется метод valueOf, а если его нет — то toString.

Operation ToPrimitive

Converting an arbitrary value to a primitive is handled via the spec-internal operation ToPrimitive(input, ?PreferredType) takes an with input argument and an optional argument PreferredType which has three modes:

  • "number": the caller needs a number.

  • "string": the caller needs a string.

  • "default": the caller needs either a number or a string. The default mode is only used by:

    • Equality operator (==)

    • Addition operator (+)

    • new Date(value) (exactly one parameter!)

If the value is a primitive then ToPrimitive() is already done. Otherwise, the value is an object obj, which is converted to a primitive as follows:

  • Number mode: Return the result of obj.valueOf() if it is primitive. Otherwise, return the result of obj.toString() if it is primitive. Otherwise, throw a TypeError.

  • String mode: works like Number mode, but toString() is called first, valueOf() second.

  • Default mode: works exactly like Number mode.

toPrimitive operation in the spec

The abstract operation ToPrimitive converts its input argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favour that type. Conversion occurs according to the following algorithm:

  1. If Type(input) is Object, then:

    1. If PreferredType was not passed, let hint be "default".

    2. Else if PreferredType is hint String, let hint be "string".

    3. Else PreferredType is hint Number, let hint be "number".

    4. Let exoticToPrim be ?GetMethod(input, @@toPrimitive).

    5. If exoticToPrim is not undefined, then

      1. Let result be Call(exoticToPrim, input, hint).

      2. If Type(result) is not Object, return result.

      3. Throw a TypeError exception.

    6. If hint is "default", set hint to "number".

    7. Return OrdinaryToPrimitive(input, hint).

  2. Return input.

When the abstract operation OrdinaryToPrimitive(O, hint) is called with arguments O and hint, the following steps are taken:

  1. Assert: Type(O) is Object.

  2. Assert: Type(hint) is String and its value is either "string" or "number".

  3. If hint is "string", then:

    1. Let methodNames be ["toString", "valueOf"].

  4. Else,

    1. Let methodNames be ["valueOf", "toString"].

  5. For each name in methodNames do

    1. Let method be Get(O, name).

    2. If IsCallable(method) is true, then

      1. Let result be Call(method, O).

      2. If Type(result) is not Object, return result.

  6. Throw a TypeError exception.

Symbol @@toPrimitive

Symbol.toPrimitive lets an object customize how it is coerced (converted automatically) to a primitive value. Значением этого символа должна быть функция, возвращающая значение одного из примитивных типов. Эта функция вызывается в качестве первой при преобразовании объекта в примитив по алгоритму ToPrimitive и имеет приоритет над другими функциями преобразования.

// An object without Symbol.toPrimitive property.
const obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"

// An object with Symbol.toPrimitive property.
const obj2 = {
  [Symbol.toPrimitive](hint) {
    if (hint === "number") {
      return 10;
    }
    if (hint === "string") {
      return "hello";
    }
    return true;
  }
};
console.log(+obj2); // 10        -- hint is "number"
console.log(`${obj2}`); // "hello"   -- hint is "string"
console.log(obj2 + ""); // "true"    -- hint is "default"

Operations toString and valueOf

Все объекты наследуют два метода преобразования:

  • Метод toString() он возвращает строковое представление объекта. Если переменная является объектом, то по умолчанию метод toString выводит [object <Tип>]. Обратиться к этой нативной реализации toString можно Object.prototype.toString.call(obj).

  • Метод valueOf(). Задача этого метода определена нечетко: предполагается, что он должен преобразовать объект в представляющее его простое значение, если такое значение существует. Объекты по своей природе являются составными значениями, и большинство объектов не могут быть представлены в виде единственного простого значения, поэтому по умолчанию метод valueOf() возвращает не простое значение, а сам объект (this). Метод valueOf обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом — не обязательно числовое.

> const empty = {};
> empty.toString() // => '[object Object]'

> const empty = {};
> empty.valueOf() === empty // => true

Примеры использования valueOf:

  • Классы-обертки определяют методы valueOf(), возвращающие обернутые простые значения.

  • Массивы, функции и регулярные выражения наследуют метод по умолчанию. Вызов метода valueOf() экземпляров этих типов возвращает сам объект.

  • Класс Date определяет метод valueOf(), возвращающий дату во внутреннем представлении: количество миллисекунд, прошедших с 1 января 1970 года.

Last updated