Ранее, в главе Преобразование типов для примитивов мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты.
Бывают операции, при которых объект должен быть преобразован в примитив.
Например:
- Строковое преобразование – если объект выводится через
alert(obj). - Численное преобразование – при арифметических операциях, сравнении с примитивом.
- Логическое преобразование – при
if(obj)и других логических операциях.
Рассмотрим эти преобразования по очереди.
Логическое преобразование
Проще всего – с логическим преобразованием.
Любой объект в логическом контексте – true, даже если это пустой массив [] или объект {}.
Строковое преобразование
Строковое преобразование проще всего увидеть, если вывести объект при помощи alert:
Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка "[object Object]".
Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.
Если в объекте присутствует метод toString, который возвращает примитив, то он используется для преобразования.
toString может быть любой примитивМетод toString не обязан возвращать именно строку.
Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже:
Поэтому мы и называем его здесь «строковое преобразование», а не «преобразование к строке».
Все объекты, включая встроенные, имеют свои реализации метода toString, например:
Численное преобразование
Для численного преобразования объекта используется метод valueOf, а если его нет – то toString:
Метод valueOf обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом – не обязательно числовое.
valueOfУ большинства встроенных объектов такого valueOf нет, поэтому численное и строковое преобразования для них работают одинаково.
Исключением является объект Date, который поддерживает оба типа преобразований:
Если посмотреть в стандарт, то в пункте 15.2.4.4 говорится о том, что valueOf есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (непримитивное значение!), а потому игнорируется.
Две стадии преобразования
Итак, объект преобразован в примитив при помощи toString или valueOf.
Но на этом преобразования не обязательно заканчиваются. Вполне возможно, что в процессе вычислений этот примитив будет преобразован во что-то другое.
Например, рассмотрим применение к объекту операции ==:
Объект obj был сначала преобразован в примитив, используя численное преобразование, получилось 1 == true.
Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: true.
То же самое – при сложении с объектом при помощи +:
Или вот, для разности объектов:
DateОбъект Date по историческим причинам является исключением.
Бинарный оператор плюс + обычно использует численное преобразование и метод valueOf. Как мы уже знаем, если подходящего valueOf нет (а его нет у большинства объектов), то используется toString, так что в итоге преобразование происходит к строке. Но если есть valueOf, то используется valueOf. Выше в примере как раз a + b это демонстрируют.
У объектов Date есть и valueOf – возвращает количество миллисекунд, и toString – возвращает строку с датой.
...Но оператор + для Date использует именно toString (хотя должен бы valueOf).
Это и есть исключение:
Других подобных исключений нет.
В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис new Boolean(true/false), например new Boolean(true).
В JavaScript тоже есть подобная возможность, которая возвращает «объектную обёртку» для логического значения.
Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например:
Почему запустился alert? Ведь в if находится false... Проверим:
Дело в том, что new Boolean – это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к true, в результате работает первый пример.
А второй пример вызывает alert, который преобразует объект к строке, и он становится "false".
В JavaScript вызовы new Boolean/String/Number не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например Boolean(val) === !!val.
Итого
- В логическом контексте объект – всегда
true. - При строковом преобразовании объекта используется его метод
toString. Он должен возвращать примитивное значение, причём не обязательно именно строку. - Для численного преобразования используется метод
valueOf, который также может возвратить любое примитивное значение. У большинства объектовvalueOfне работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используетсяtoString.
Полный алгоритм преобразований есть в спецификации ECMAScript, смотрите пункты 11.8.5, 11.9.3, а также 9.1 и 9.3.
Заметим, для полноты картины, что некоторые тесты знаний в интернет предлагают вопросы типа:
{}[0] // чему равно?
{} + {} // а так?
Если вы запустите эти выражения в консоли, то результат может показаться странным. Подвох здесь в том, что если фигурные скобки {...} идут не в выражении, а в основном потоке кода, то JavaScript считает, что это не объект, а «блок кода» (как if, for, но без оператора просто группировка команд вместе используется редко).
Вот блок кода с командой:
А если команду изъять, то будет пустой блок {}, который ничего не делает. Два примера выше как раз содержат пустой блок в начале, который ничего не делает. Иначе говоря:
{}[0] // то же что и: [0]
{} + {} // то же что и: + {}
То есть, такие вопросы – не на преобразование типов, а на понимание, что если { ... } находится вне выражений, то это не объект, а блок.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen...)