# Преобразование объектов: toString и valueOf Ранее, в главе [](/types-conversion) мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты. Бывают операции, при которых объект должен быть преобразован в примитив. [cut] Например: Рассмотрим эти преобразования по очереди. ## Логическое преобразование Проще всего -- с логическим преобразованием. **Любой объект в логическом контексте -- `true`, даже если это пустой массив `[]` или объект `{}`.** ```js //+ run if ( {} && [] ) { alert("Все объекты - true!"); // alert сработает } ``` ## Строковое преобразование Строковое преобразование проще всего увидеть, если вывести объект при помощи `alert`: ```js //+ run var user = { firstName: 'Василий' }; alert(user); // [object Object] ``` Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`. Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное. **Если в объекте присутствует метод `toString`, который возвращает примитив, то он используется для преобразования.** ```js //+ run var user = { firstName: 'Василий', *!*toString:*/!* function() { return 'Пользователь ' + this.firstName; } }; alert( user ); // Пользователь Василий ``` [smart header="Результатом `toString` может быть любой примитив"] Метод `toString` не обязан возвращать именно строку. Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже: ```js //+ run var obj = { toString: function() { return 123; } }; alert(obj); // 123 ``` Поэтому мы и называем его здесь *"строковое преобразование"*, а не "преобразование к строке". [/smart] Все объекты, включая встроенные, имеют свои реализации метода `toString`, например: ```js //+ run alert( [1,2] ); // toString для массивов выводит список элементов "1,2" alert( new Date ); // toString для дат выводит дату в виде строки alert( function() { } ); // toString для функции выводит её код ``` ## Численное преобразование Для численного преобразования объекта используется метод `valueOf`, а если его нет -- то `toString`: ```js //+ run var room = { number: 777, valueOf: function() { return this.number; }, toString: function() { return this.number; } }; alert( +room ); // 777, *!*вызвался valueOf*/!* delete room.valueOf; // *!*valueOf удалён*/!* alert( +room ); // 777, *!*вызвался toString*/!* ``` Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое. [smart header="У большинства объектов нет `valueOf`"] У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково. Исключением является объект `Date`, который поддерживает оба типа преобразований: ```js //+ run alert( new Date() ); // toString: Дата в виде читаемой строки alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970 ``` [/smart] [smart header="Детали спецификации"] Если посмотреть в стандарт, то в пункте [15.2.4.4](http://es5.github.com/x15.2.html#x15.2.4.4) говорится о том, что `valueOf` есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется. [/smart] ## Две стадии преобразования Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. Далее, вполне возможно, Если необходимо, что полученный из объекта примитив будет преобразован дальше, уже по правилам для примитивов. Например, рассмотрим применение к объекту операции `==`: ```js //+ run var obj = { valueOf: function() { return 1; } }; alert(obj == true); // true ``` Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`. Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: `true`. То же самое -- при сложении с объектом при помощи `+`: ```js //+ run var obj = { valueOf: function() { return 1; } }; alert(obj + "test"); // 1test ``` Или вот, для разности объектов: ```js //+ run var a = { valueOf: function() { return "1"; } }; var b = { valueOf: function() { return "2"; } }; alert(a - b); // "1" - "2" = -1 ``` [warn header="Исключение: `Date`"] Объект `Date`, по историческим причинам, является исключением. Бинарный оператор плюс `+` обычно использует числовое преобразование, но в случае с `Date` -- строковое: ```js //+ run // бинарный вариант, строчное преобразование alert( new Date + "" ); // "строка даты" // унарный вариант, как и - * /, приводит к числу alert( +new Date ); // число миллисекунд ``` [/warn] [warn header="Как испугать Java-разработчика"] В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис `new Boolean(true/false)`, например `new Boolean(true)`. В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения. Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например: ```js //+ run var value = new Boolean(false); if ( value ) { alert(true); // сработает! } ``` Почему запустился `alert`? Ведь в `if` находится `false`... Проверим: ```js //+ run var value = new Boolean(false); *!* alert(value); // выводит false, все ок.. */!* if ( value ) { alert(true); // ..но тогда почему выполняется alert в if ?!? } ``` Дело в том, что `new Boolean` -- это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к`true`, в результате работает первый пример. А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`. **В JavaScript вызовы `new Boolean/String/Number` не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например `Boolean(val) === !!val`.** [/warn] ## Итого Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3). Заметим, для полноты картины, что некоторые тесты знаний в интернет предлагают вопросы типа: ```js {}[0] // чему равно? {} + {} // а так? ``` Если вы запустите эти выражения в консоли, то результат может показаться странным. Подвох здесь в том, что если фигурные скобки `{...}` идут не в выражении, а в основном потоке кода, то JavaScript считает, что это не объект, а "блок кода" (как `if`, `for`, но без оператора, просто группировка команд вместе, используется редко). Вот блок кода с командой: ```js //+run { alert("Блок") } ``` А если команду изъять, то будет пустой блок `{}`, который ничего не делает. Два примера выше как раз содержат пустой блок в начале, который ничего не делает. Иначе говоря: ```js {}[0] // то же что и: [0] {} + {} // то же что и: + {} ``` То есть, такие вопросы -- не на преобразование типов, а на понимание, что если `{ ... }` находится вне выражений, то это не объект, а блок.