W3docs

Преобразование object в примитив в JavaScript

Узнайте, как JavaScript преобразует объекты в примитивы: подсказки string, number и default, метод Symbol.toPrimitive, цепочка toString/valueOf с примерами.

Введение в преобразование object в примитив

В JavaScript объекты являются ссылочными значениями, однако многие операции ожидают примитив (string, число или boolean). Когда вы пишете obj + "", +obj или `${obj}`, язык должен сначала преобразовать объект в примитив, прежде чем выполнить операцию. Это называется преобразованием object в примитив.

В этом руководстве объясняются правила, которым следует JavaScript: три подсказки преобразования ("string", "number", "default"), метод Symbol.toPrimitive, позволяющий управлять преобразованием, и резервная цепочка toString()/valueOf(), которая используется при отсутствии Symbol.toPrimitive.

Как работает преобразование object в примитив

Не существует оператора, который преобразовывал бы объект в boolean — объекты в boolean-контексте всегда истинны. Поэтому преобразование object в примитив всегда даёт string или число, а JavaScript решает, к чему стремиться, передавая объекту подсказку:

  1. Сначала движок ищет метод [Symbol.toPrimitive](hint). Если он есть, он вызывается, и его возвращаемое значение (которое должно быть примитивом) используется как результат.
  2. Если Symbol.toPrimitive отсутствует, JavaScript обращается к toString() и valueOf(), вызывая их в порядке, зависящем от подсказки.

Резервный механизм будет рассмотрен подробнее позже. Сначала — современный, явный подход.

Пример: реализация Symbol.toPrimitive

javascript— editable

Пояснение: Объект user определяет единственный метод Symbol.toPrimitive, который ветвится по подсказке. Шаблонная строка запрашивает подсказку "string", умножение — "number", а бинарный оператор +"default". Возврат this.money для случая default обеспечивает согласованность арифметики с + и *.

Понимание подсказок преобразования

Подсказка — это string, который движок передаёт объекту, чтобы указать, какой тип примитива предпочтительнее для данной операции:

  • "string": ожидается string-результат — String(obj), `${obj}`, alert(obj) или объект, используемый как ключ свойства.
  • "number": ожидается числовой результат — унарный +obj, obj * 2, obj - 1, obj < other, Number(obj), Math.round(obj).
  • "default": оператор принимает любой тип и не уверен, какой запрашивать. Это встречается реже, чем принято думать, но важно: бинарный оператор + (который может означать как сложение, так и конкатенацию string) использует "default", как и операторы нестрогого равенства == / != при сравнении объекта с числом или string.

Распространённый сюрприз: obj + "" использует не подсказку "string" — а подсказку "default". Если вы обрабатываете только "string" и "number", именно ветвь "default" будет выполняться для +.

Пример: обработка разных подсказок

javascript— editable

Пояснение: Объект item обрабатывает все три подсказки. Обратите внимание на последнюю строку: поскольку бинарный оператор + использует подсказку "default", выражение item + '' выполняет ветвь "default", а не "string", и возвращает "Item: Chair, Price: 45". Именно такие тонкости делают явную обработку каждой подсказки оправданной. Смотрите также операторы сравнения и числовые операторы.

Резервная цепочка toString / valueOf

Если у объекта нет метода Symbol.toPrimitive, JavaScript использует пару более старых методов и выбирает порядок их вызова в зависимости от подсказки:

  • Для подсказки "string": сначала пробуется toString(), затем valueOf().
  • Для подсказок "number" или "default": сначала пробуется valueOf(), затем toString().

В каждом случае используется первый метод, вернувший примитив; если метод возвращает объект, он пропускается и пробуется следующий. Обычный объект наследует Object.prototype.toString (возвращающий "[object Object]") и Object.prototype.valueOf (возвращающий сам объект, поэтому он игнорируется) — именно поэтому ({}) + "" равно "[object Object]".

javascript— editable

Пояснение: При отсутствии Symbol.toPrimitive подсказка "string" ведёт к toString() и возвращает "John", а числовые и default-подсказки ведут к valueOf() и возвращают 1000. Symbol.toPrimitive предпочтителен в новом коде, поскольку даёт единственное явное место для обработки всех подсказок; toString/valueOf остаются полезными, когда вам важно только одно направление преобразования.

Рекомендации по использованию toPrimitive

Эффективная реализация Symbol.toPrimitive сочетает в себе ясность, согласованность и тщательное тестирование, чтобы объекты вели себя предсказуемо при преобразовании в примитивы. Вот как применять эти рекомендации при использовании метода Symbol.toPrimitive:

1. Чёткая семантика

Рекомендация: Определяйте Symbol.toPrimitive ясно, чтобы преобразования объектов были предсказуемы и понятны. Это подразумевает явную обработку разных типов подсказок ("string", "number" и "default") и возврат подходящих значений для каждого случая.

Пример:

javascript— editable

Пояснение: В этом примере объект dateEvent явно определяет поведение преобразования для контекстов string и числа. Для string-преобразований он возвращает описательную строку, а для числовых — метку времени события. Такое чёткое разделение помогает другим разработчикам понять, что ожидать при преобразовании объекта в разных контекстах.

2. Согласованность

Рекомендация: Убедитесь, что преобразования согласованы с данными объекта и его назначением, избегая запутанного или нелогичного поведения.

javascript— editable

Пояснение: Объект product гарантирует, что логика преобразования согласована с его свойствами. Будь то преобразование в string для отображения или в число для вычислений — результат остаётся интуитивно понятным и полезным, соответствуя назначению каждого свойства.

3. Тестирование

Рекомендация: Тщательно тестируйте поведение объектов в различных сценариях преобразования, чтобы избежать неожиданных ошибок в приложении.

Примеры подходов к тестированию:

  • Unit-тесты: Пишите unit-тесты, которые преобразуют объект с помощью различных операций (арифметических, конкатенации string или передачи объекта в функции, ожидающие примитивный тип), чтобы убедиться, что все сценарии возвращают ожидаемые значения.
// Note: In a browser environment, use console.assert or a test framework like Jest/Mocha.
// Assumes 'product' is defined as in the previous example.
console.assert(String(product) === "Laptop costs $1200", "String conversion failed");
console.assert(+product === 1200, "Number conversion failed");
console.assert(product + '' === "Laptop", "Default conversion failed");

Пояснение: С помощью unit-тестов вы можете убедиться, что объект product правильно обрабатывает все виды преобразований согласно логике, заданной в Symbol.toPrimitive. Это обеспечивает надёжность и согласованность взаимодействия объекта с разными частями движка JavaScript и вашего приложения.

Частые ошибки

  • Подсказки boolean не существует. В boolean-контексте (if (obj), !obj, obj && x) объект всегда истинен и никогда не преобразуется в примитив. Преобразование object в примитив даёт только string и числа.
  • + использует "default", а не "string". Это сбивает с толку многих разработчиков: obj + "" активирует подсказку default. Сравнения вида obj == 5 тоже используют "default".
  • Метод должен возвращать примитив. Если Symbol.toPrimitive (или valueOf/toString) возвращает объект вместо примитива, возникает TypeError. В резервной паре возврат объекта просто приводит к пропуску этого метода.
  • Числовое преобразование string-результата может дать NaN. Если ветвь "number"/"default" возвращает не числовую строку, контексты, ожидающие число, получат NaN: +{ [Symbol.toPrimitive]: () => "abc" } равно NaN.

Заключение

Преобразование object в примитив — это основной механизм JavaScript, позволяющий объектам участвовать в арифметических операциях, конкатенации string и операциях сравнения. Движок выбирает подсказку ("string", "number" или "default"), сначала пробует Symbol.toPrimitive, а при его отсутствии обращается к toString()/valueOf(). Реализовав Symbol.toPrimitive, вы получаете единственное явное место для управления поведением пользовательского объекта в любом контексте — что ведёт к более предсказуемому и поддерживаемому коду. Для более глубокого изучения обратитесь к разделам типы данных, символьные типы и методы объекта и this.

Практика

Практика
Когда вы вычисляете obj + '' и у obj есть метод Symbol.toPrimitive, какую подсказку передаёт JavaScript?
Когда вы вычисляете obj + '' и у obj есть метод Symbol.toPrimitive, какую подсказку передаёт JavaScript?
Практика
Если у объекта нет метода Symbol.toPrimitive, в каком порядке JavaScript пробует методы для подсказки 'number'?
Если у объекта нет метода Symbol.toPrimitive, в каком порядке JavaScript пробует методы для подсказки 'number'?
Was this page helpful?