Ты пишешь класс Money для финансового приложения. Хочется, чтобы price + 500 вернуло число, а `Цена: ${price} вернуло строку "2500 ₽". JavaScript позволяет это через механизм Symbol.toPrimitive` — одну точку управления всеми видами преобразований.
const obj = { value: 42 }
obj + 10 // арифметика → числовой хинт
`Цена: ${obj}` // шаблонная строка → строковый хинт
if (obj) { } // логический контекст → всегда true (объекты truthy)JS передаёт в метод конвертации один из трёх хинтов:
+, арифметика, Math.sqrt(obj)${obj}, String(obj), alert(obj)+, == с числомSymbol.toPrimitive — встроенный символ. Если он определён на объекте, JS вызывает его при любой конвертации:
const money = {
amount: 1500,
currency: 'RUB',
[Symbol.toPrimitive](hint) {
if (hint === 'string') return `${this.amount} ${this.currency}`
if (hint === 'number') return this.amount
return this.amount // 'default' — ведём себя как число
}
}
console.log(`Цена: ${money}`) // 'Цена: 1500 RUB' (hint: 'string')
console.log(money + 500) // 2000 (hint: 'default')
console.log(+money) // 1500 (hint: 'number')Если Symbol.toPrimitive не определён, JS пробует:
1. Для хинта "string": сначала toString(), потом valueOf()
2. Для хинтов "number" и "default": сначала valueOf(), потом toString()
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return `Point(${this.x}, ${this.y})`
}
valueOf() {
return Math.sqrt(this.x ** 2 + this.y ** 2) // расстояние от начала координат
}
}
const p = new Point(3, 4)
console.log(String(p)) // 'Point(3, 4)' → toString()
console.log(p + 0) // 5 → valueOf()
console.log(`${p}`) // 'Point(3, 4)' → toString()| Хинт | Пробуется сначала | Потом |
|------|------------------|-------|
| "string" | toString() | valueOf() |
| "number" | valueOf() | toString() |
| "default" | valueOf() | toString() |
Если Symbol.toPrimitive есть — все остальные игнорируются.
1. Забыть вернуть значение из Symbol.toPrimitive — получишь TypeError:
// Плохо: нет return для хинта 'number'
const bad = {
[Symbol.toPrimitive](hint) {
if (hint === 'string') return 'текст'
// забыли вернуть число!
}
}
console.log(+bad) // NaN — возвращает undefined, который превращается в NaN
// Хорошо: обрабатываем все хинты
const good = {
[Symbol.toPrimitive](hint) {
if (hint === 'string') return 'текст'
return 42 // number и default
}
}
console.log(+good) // 422. valueOf возвращает объект — JS не сможет преобразовать:
// Плохо: valueOf возвращает объект
class Bad {
valueOf() { return this } // ошибка! JS ожидает примитив
}
const b = new Bad()
console.log(b + 1) // '[object Object]1' — JS пошёл в toString()
// Хорошо: valueOf возвращает примитив
class Good {
constructor(n) { this.n = n }
valueOf() { return this.n }
}
console.log(new Good(5) + 1) // 63. Шаблонная строка использует хинт "string", а не "default":
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'default') return 'дефолт'
if (hint === 'string') return 'строка'
return 0
}
}
console.log(`${obj}`) // 'строка' — хинт 'string'
console.log(obj + '') // 'дефолт' — хинт 'default' (бинарный +)Money, Price, Currency: арифметика через valueOf, форматирование через toStringVector, Point: длина/модуль через valueOfDuration, Temperature: сравнение и вычитание без ручного преобразованияКласс Money с Symbol.toPrimitive для арифметики и форматированного строкового вывода
class Money {
constructor(amount, currency = 'RUB') {
this.amount = amount
this.currency = currency
}
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: this.currency,
maximumFractionDigits: 0,
}).format(this.amount)
}
// 'number' и 'default' → числовое значение
return this.amount
}
add(other) {
const otherAmount = other instanceof Money ? other.amount : other
return new Money(this.amount + otherAmount, this.currency)
}
}
const price = new Money(2500)
const shipping = new Money(350)
console.log(`Товар: ${price}`) // 'Товар: 2 500 ₽'
console.log(`Доставка: ${shipping}`) // 'Доставка: 350 ₽'
const total = price.add(shipping)
console.log(`Итого: ${total}`) // 'Итого: 2 850 ₽'
// Арифметика через Symbol.toPrimitive с hint 'default'
console.log(price + 100) // 2600 (число)
console.log(price > 1000) // true
console.log(+shipping) // 350
// Класс Temperature
class Temperature {
constructor(celsius) { this.celsius = celsius }
get fahrenheit() { return this.celsius * 9 / 5 + 32 }
[Symbol.toPrimitive](hint) {
if (hint === 'string') return `${this.celsius}°C (${this.fahrenheit.toFixed(1)}°F)`
return this.celsius
}
}
const bodyTemp = new Temperature(36.6)
console.log(`Температура тела: ${bodyTemp}`) // '36.6°C (97.9°F)'
console.log(bodyTemp > 37) // false — нет лихорадки
console.log(bodyTemp + 1.5) // 38.1Ты пишешь класс Money для финансового приложения. Хочется, чтобы price + 500 вернуло число, а `Цена: ${price} вернуло строку "2500 ₽". JavaScript позволяет это через механизм Symbol.toPrimitive` — одну точку управления всеми видами преобразований.
const obj = { value: 42 }
obj + 10 // арифметика → числовой хинт
`Цена: ${obj}` // шаблонная строка → строковый хинт
if (obj) { } // логический контекст → всегда true (объекты truthy)JS передаёт в метод конвертации один из трёх хинтов:
+, арифметика, Math.sqrt(obj)${obj}, String(obj), alert(obj)+, == с числомSymbol.toPrimitive — встроенный символ. Если он определён на объекте, JS вызывает его при любой конвертации:
const money = {
amount: 1500,
currency: 'RUB',
[Symbol.toPrimitive](hint) {
if (hint === 'string') return `${this.amount} ${this.currency}`
if (hint === 'number') return this.amount
return this.amount // 'default' — ведём себя как число
}
}
console.log(`Цена: ${money}`) // 'Цена: 1500 RUB' (hint: 'string')
console.log(money + 500) // 2000 (hint: 'default')
console.log(+money) // 1500 (hint: 'number')Если Symbol.toPrimitive не определён, JS пробует:
1. Для хинта "string": сначала toString(), потом valueOf()
2. Для хинтов "number" и "default": сначала valueOf(), потом toString()
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return `Point(${this.x}, ${this.y})`
}
valueOf() {
return Math.sqrt(this.x ** 2 + this.y ** 2) // расстояние от начала координат
}
}
const p = new Point(3, 4)
console.log(String(p)) // 'Point(3, 4)' → toString()
console.log(p + 0) // 5 → valueOf()
console.log(`${p}`) // 'Point(3, 4)' → toString()| Хинт | Пробуется сначала | Потом |
|------|------------------|-------|
| "string" | toString() | valueOf() |
| "number" | valueOf() | toString() |
| "default" | valueOf() | toString() |
Если Symbol.toPrimitive есть — все остальные игнорируются.
1. Забыть вернуть значение из Symbol.toPrimitive — получишь TypeError:
// Плохо: нет return для хинта 'number'
const bad = {
[Symbol.toPrimitive](hint) {
if (hint === 'string') return 'текст'
// забыли вернуть число!
}
}
console.log(+bad) // NaN — возвращает undefined, который превращается в NaN
// Хорошо: обрабатываем все хинты
const good = {
[Symbol.toPrimitive](hint) {
if (hint === 'string') return 'текст'
return 42 // number и default
}
}
console.log(+good) // 422. valueOf возвращает объект — JS не сможет преобразовать:
// Плохо: valueOf возвращает объект
class Bad {
valueOf() { return this } // ошибка! JS ожидает примитив
}
const b = new Bad()
console.log(b + 1) // '[object Object]1' — JS пошёл в toString()
// Хорошо: valueOf возвращает примитив
class Good {
constructor(n) { this.n = n }
valueOf() { return this.n }
}
console.log(new Good(5) + 1) // 63. Шаблонная строка использует хинт "string", а не "default":
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'default') return 'дефолт'
if (hint === 'string') return 'строка'
return 0
}
}
console.log(`${obj}`) // 'строка' — хинт 'string'
console.log(obj + '') // 'дефолт' — хинт 'default' (бинарный +)Money, Price, Currency: арифметика через valueOf, форматирование через toStringVector, Point: длина/модуль через valueOfDuration, Temperature: сравнение и вычитание без ручного преобразованияКласс Money с Symbol.toPrimitive для арифметики и форматированного строкового вывода
class Money {
constructor(amount, currency = 'RUB') {
this.amount = amount
this.currency = currency
}
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: this.currency,
maximumFractionDigits: 0,
}).format(this.amount)
}
// 'number' и 'default' → числовое значение
return this.amount
}
add(other) {
const otherAmount = other instanceof Money ? other.amount : other
return new Money(this.amount + otherAmount, this.currency)
}
}
const price = new Money(2500)
const shipping = new Money(350)
console.log(`Товар: ${price}`) // 'Товар: 2 500 ₽'
console.log(`Доставка: ${shipping}`) // 'Доставка: 350 ₽'
const total = price.add(shipping)
console.log(`Итого: ${total}`) // 'Итого: 2 850 ₽'
// Арифметика через Symbol.toPrimitive с hint 'default'
console.log(price + 100) // 2600 (число)
console.log(price > 1000) // true
console.log(+shipping) // 350
// Класс Temperature
class Temperature {
constructor(celsius) { this.celsius = celsius }
get fahrenheit() { return this.celsius * 9 / 5 + 32 }
[Symbol.toPrimitive](hint) {
if (hint === 'string') return `${this.celsius}°C (${this.fahrenheit.toFixed(1)}°F)`
return this.celsius
}
}
const bodyTemp = new Temperature(36.6)
console.log(`Температура тела: ${bodyTemp}`) // '36.6°C (97.9°F)'
console.log(bodyTemp > 37) // false — нет лихорадки
console.log(bodyTemp + 1.5) // 38.1В навигационном приложении используется класс Vector2D. Добавь Symbol.toPrimitive так, чтобы: при строковом хинте возвращалась строка вида "(x, y)", а при числовом и default хинте — длина вектора (Math.sqrt(x²+y²)).
hint === "string" → вернуть `(${this.x}, ${this.y})`. Иначе — Math.sqrt(this.x ** 2 + this.y ** 2). Шаблонная строка `${v}` использует хинт "string", а v + 0 — хинт "default".