Ранее, в главе Преобразование типов для примитивов мы рассматривали преобразование типов, уделяя основное внимание примитивам. Теперь добавим в нашу стройную картину преобразования типов объекты.
При этом будут следующие изменения:
- В объектах численное и строковое преобразования можно переопределить.
- Если операция требует примитивное значение, то сначала объект преобразуется к примитиву, а затем — все остальное.
При этом для преобразования к примитиву используется численное преобразование.
Для того, чтобы лучше интегрировать их в общую картину, рассмотрим примеры.
Строковое преобразование
Строковое преобразование проще всего увидеть, если вывести объект при помощи 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)