Ранее, в главе Преобразование типов для примитивов мы рассматривали преобразование типов, уделяя основное внимание примитивам. Теперь добавим в нашу стройную картину преобразования типов объекты.
При этом будут следующие изменения:
- В объектах численное и строковое преобразования можно переопределить.
- Если операция требует примитивное значение, то сначала объект преобразуется к примитиву, а затем — все остальное.
При этом для преобразования к примитиву используется численное преобразование.
Для того, чтобы лучше интегрировать их в общую картину, рассмотрим примеры.
Строковое преобразование
Строковое преобразование проще всего увидеть, если вывести объект при помощи alert:
Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка "[object Object]".
Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.
Если в объекте присутствует метод toString, который возвращает примитив, то он используется для преобразования.
Метод toString не обязан возвращать именно строку.
Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже:
Поэтому мы и называем его здесь «строковое преобразование», а не «преобразование к строке».
Все объекты, включая встроенные, имеют свои реализации метода toString, например:
Численное преобразование
Для численного преобразования объекта используется метод valueOf, а если его нет — тоtoString:
Метод valueOf обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом — не обязательно числовое.
У большинства встроенных объектов такого valueOf нет, поэтому численное и строковое преобразования для них работают одинаково.
Исключением является объект Date, который поддерживает оба типа преобразований:
Если посмотреть в стандарт, то в 15.2.4.4 определен valueOf для любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется.
Преобразование в примитив
Большинство операций, которые ожидают примитивное значение, при виде объекта сначала преобразуют его к примитиву.
Например, арифметическая операция или сравнение > < >= <= сначала преобразует объект в примитив.
А затем уже идет операция с примитивами, при этом возможны последующие преобразования.
При приведении объекта к примитиву используется численное преобразование.
Например:
Пример ниже демонстрирует, что несмотря на то, что приведение «численное» — его результатом может быть любое примитивное значение, не обязательно число:
После того, как объект приведен к примитиву, могут быть дополнительные преобразования. Об этом говорит сайт https://intellect.icu . Например:
Существует исключение: объект Date преобразуется в примитив, используя строковое преобразование. С этим можно столкнуться в операторе "+":
Это исключение явно прописано в стандарте и является единственным в своем роде.
В языке Java логические значения можно создавать, используя синтаксисnew Boolean(true/false). Также можно преобразовывать значения к логическому типу, применяя к ним new Boolean.
В JavaScript тоже есть подобная возможность, которая возвращает «объектную обертку» для логического значения. Эта возможность сохраняется для совместимости и не используется на практике, поскольку приводит к странным результатам.
Например:
Почему запустился alert? Ведь в if находится false… Проверим:
Дело в том, что new Boolean - это объект. В логическом контексте он, безусловно,true. Поэтому работает первый пример.
А второй пример вызывает alert, который преобразует объект к строке, и он становится "false".
Чтобы преобразовать значение к логическому типу, нужно использовать двойное отрицание: !!val или прямой вызов Boolean(val).
Итого
- При строковом преобразовании объекта используется его метод
toString. Он должен возвращать примитивное значение, причем не обязательно именно строку.
В стандарте прописано, что если toString нет, или он возвращает не примитив, а объект, то вызывается valueOf, но обычно toString есть.
- При численном преобразовании объекта используется метод
valueOf, а если его нет, тоtoString. У встроенных объектов valueOf обычно нет.
- При операции над объектом, которая требует примитивное значение, объект первым делом преобразуется в примитив. Для этого используется численное преобразование, исключение — встроенный объект
Date.
Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты 11.8.5,11.9.3, а также 9.1 и 9.3.
Важность: 5
Если с одной стороны — объект, а с другой — нет, то сначала приводится объект.
В данном случае сравнение означает численное приведение. У массивов нетvalueOf, поэтому вызывается toString, который возвращает список элементов через запятую.
В данном случае, элемент только один - он и возвращается. Так что ['x'] становится'x'. Получилось 'x' == 'x', верно.
P.S.
По той же причине верны равенства:
[Открыть задачу в новом окне]
Важность: 5
Ответы, один за другим.
alert(foo)- Возвращает строковое представление объекта, используя
toString, т.е. "foo". alert(foo + 1)- Оператор
'+' преобразует объект к примитиву, используя valueOf, так что результат: 3. alert(foo + '3')- То же самое, что и предыдущий случай, объект превращается в примитив
2. Затем происходит сложение 2 + '3'. Оператор '+' при сложении чего-либо со строкой приводит и второй операнд к строке, а затем применяет конкатенацию, так что результат — строка "23".
[Открыть задачу в новом окне]
Важность: 5
Два объекта равны только тогда, когда это один и тот же объект.
В первом равенстве создаются два массива, это разные объекты, так что они неравны.
Ответ по второму равенству
[Открыть задачу в новом окне]
Важность: 5
2 |
new Array(1)[0] + "" = "undefined" |
5 |
[1,2] + [3,4] = "1,23,4" |
6 |
[] + null + 1 = "null1" |
8 |
({} + {}) = "[object Object][object Object]" |
new Date(0) — дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус - преобразует дату обратно в число миллисекунд, то есть в 0.
new Array(num) при вызове с единственным аргументом-числом создает массив данной длины, без элементов. Поэтому его нулевой элемент равенundefined, при сложении со строкой получается строка "undefined".
- Фигурные скобки — это создание пустого объекта, у него нет свойства
'0'. Так что значением будет undefined.
Обратите внимание на внешние, круглые скобки. Если их убрать и запустить{}[0] в отладочной консоли браузера — будет 0, т.к. скобки {} будут восприняты как пустой блок кода, после которого идет массив.
- Массив преобразуется в строку
"1". Оператор "+" при сложении со строкой приводит второй аргумент к строке — значит будет "1" + "1" = "11".
- Массивы приводятся к строке и складываются.
- Массив преобразуется в пустую строку
"" + null + 1, оператор "+" видит, что слева строка и преобразует null к строке, получается "null" + 1, и в итоге"null1".
[[0]] — это вложенный массив [0] внутри внешнего [ ]. Затем мы берем от него нулевой элемент, и потом еще раз.
Если это непонятно, то посмотрите на такой пример:
Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента.
- Каждый объект преобразуется к примитиву. У встроенных объектов
Object нет подходящего valueOf, поэтому используется toString, так что складываются в итоге строковые представления объектов.
[Открыть задачу в новом окне]
Важность: 2
Чтобы sum(1), а также sum(1)(2) можно было вызвать новыми скобками — результатом sum должна быть функция.
Но эта функция также должна уметь превращаться в число. Для этого нужно дать ей соответствующий valueOf. А если мы хотим, чтобы и в строковом контексте она вела себя так же — то toString.
Функция, которая возвращается sum, должна накапливать значение при каждом вызове.
Удобнее всего хранить его в замыкании, в переменной currentSum. Каждый вызов прибавляет к ней очередное значение:
При внимательном взгляде на решение легко заметить, что функция sum срабатывает только один раз. Она возвращает функцию f.
Затем, при каждом запуске функция f добавляет параметр к сумме currentSum, хранящейся в замыкании, и возвращает сама себя.
В последней строчке f нет рекурсивного вызова.
Вот так была бы рекурсия:
А в нашем случае, мы просто возвращаем саму функцию, ничего не вызывая.
Эта f используется при следующем вызове, опять возвратит себя, и так сколько нужно раз. Затем, при использовании в строчном или численном контексте — сработает toString, который вернет текущую сумму currentSum.
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)