Преобразование типов — JavaScript — Дока
Кратко
СкопированоПредставим ситуацию: у нас есть форма с полем, в которое пользователь вписывает свой возраст в годах.
По умолчанию любой ввод в полях — это строка. Если мы хотим работать с этим значением, как с числом, то нам нужно привести его к числу.
Приведение (или преобразование) типов — это процесс конвертации значения из одного типа в другой.
В JavaScript типы можно преобразовывать явно и неявно.
Когда мы вызываем функцию, чтобы получить конкретный тип — это явное преобразование:
const x = '4'Number(x)const y = 4String(y)
const x = '4'
Number(x)
const y = 4
String(y)
Сравнение бывает строгим и нестрогим. При строгом сравнении (=
) интерпретатор учитывает типы сравниваемых значений.
Когда же мы сравниваем значения нестрого между собой с помощью =
, JavaScript приводит типы самостоятельно:
console.log(5 == '5')// trueconsole.log(5 === '5')// false console.log(5 == '5') // true console.log(5 === '5') // false
Чтобы понять, почему так, нам надо сперва разобраться, какие типы в JS есть.
Сперва проведём границу между примитивными типами, объектами и другими.
Примитивные типы
СкопированоВ JavaScript примитивные типы следующие:
// 1. Undefinedtypeof undefined === 'undefined'// 2. Boolean, логическийtypeof true === 'boolean'typeof false === 'boolean'// 3. Number, числоtypeof 42 === 'number'typeof 4.2 === 'number'typeof -42 === 'number'typeof Infinity === 'number'typeof -Infinity === 'number'// 4. String, строкаtypeof '' === 'string'typeof 'string' === 'string'typeof 'number' === 'string'typeof 'boolean' === 'string'// 5. Symbol, символ, ES6typeof Symbol() === 'symbol'// 6. BigInt, большое число, ES6typeof 9007199254740991n === 'bigint'typeof BigInt(9007199254740991) === 'bigint'// 7. Nulltypeof null === 'object'// О том, почему здесь “object” — чуть позже. // 1. Undefined
typeof undefined === 'undefined'
// 2. Boolean, логический
typeof true === 'boolean'
typeof false === 'boolean'
// 3. Number, число
typeof 42 === 'number'
typeof 4.2 === 'number'
typeof -42 === 'number'
typeof Infinity === 'number'
typeof -Infinity === 'number'
// 4. String, строка
typeof '' === 'string'
typeof 'string' === 'string'
typeof 'number' === 'string'
typeof 'boolean' === 'string'
// 5. Symbol, символ, ES6
typeof Symbol() === 'symbol'
// 6. BigInt, большое число, ES6
typeof 9007199254740991n === 'bigint'
typeof BigInt(9007199254740991) === 'bigint'
// 7. Null
typeof null === 'object'
// О том, почему здесь “object” — чуть позже.
Примитивные типы — это такие типы, значения которых можно только перезаписать, но нельзя изменить.
Например, если мы создали переменную со значением
, изменить это значение будет нельзя. Мы сможем его только полностью перезаписать:
let theAnswerToUltimateQuestion = 42theAnswerToUltimateQuestion = 43// Новое значение полностью перезаписало старое;// старое собрано сборщиком мусора и забыто. let theAnswers = [42, 43, 44]theAnswers[0] = 142// Теперь значение переменной [142, 43, 44];// мы не перезаписали его полностью, а лишь изменили часть.
let theAnswerToUltimateQuestion = 42
theAnswerToUltimateQuestion = 43
// Новое значение полностью перезаписало старое;
// старое собрано сборщиком мусора и забыто.
let theAnswers = [42, 43, 44]
theAnswers[0] = 142
// Теперь значение переменной [142, 43, 44];
// мы не перезаписали его полностью, а лишь изменили часть.
Этот механизм связан с тем, как значения переменных хранятся в памяти. Мы не пойдём слишком глубоко в эту тему, но, грубо говоря, примитивные типы «ссылаются на одно и то же значение в памяти», а не примитивные — на разные. Этот вопрос мы разбираем подробнее в статье «Хранение по ссылке и по значению »
Из-за этого, например, примитивы можно сравнивать по значению:
const a = 5const b = 5console.log(a == b)// true
const a = 5
const b = 5
console.log(a == b)
// true
А вот не примитивы — не получится:
const a = [1, 2, 3]const b = [1, 2, 3]console. log(a == b)// false
const a = [1, 2, 3]
const b = [1, 2, 3]
console.log(a == b)
// false
Даже несмотря на то, что массивы содержат одни и те же числа, при сравнении они не являются «одинаковыми». Когда JavaScript сравнивает a
и b
, он, грубо говоря, «сравнивает места в памяти, на которые ссылаются эти переменные». У не примитивов, эти места — разные, из-за чего они считаются неодинаковыми.
Объекты
СкопированоОбъекты в JavaScript используются для хранения коллекций значений.
Массивы (Array) в JS — тоже объекты.
Как мы уже говорили, не примитивы сравниваются по ссылке, а не по значению. Объекты и массивы — это как раз не примитивы.
У объектов в JavaScript собственный тип — object
.
const keyValueCollection = { key: 'value' }typeof keyValueCollection === 'object'const listCollection = [1, 2, 3]typeof listCollection === 'object'
const keyValueCollection = { key: 'value' }
typeof keyValueCollection === 'object'
const listCollection = [1, 2, 3]
typeof listCollection === 'object'
У null
оператор typeof
возвращает 'object'
, хотя это тоже примитив:
console. log(typeof null === 'object')// true
console.log(typeof null === 'object')
// true
Функции
СкопированоУ функций в JavaScript тоже тип — object
, хотя typeof
возвращает 'function'
:
function simpleFunction() {}console.log(typeof simpleFunction === 'function')// trueconst assignedFunction = function () {}console.log(typeof assignedFunction === 'function')// trueconst arrowFunction = () => {}console.log(typeof arrowFunction === 'function')// trueconsole.log(typeof function () {} === 'function')// true
function simpleFunction() {}
console.log(typeof simpleFunction === 'function')
// true
const assignedFunction = function () {}
console.log(typeof assignedFunction === 'function')
// true
const arrowFunction = () => {}
console.log(typeof arrowFunction === 'function')
// true
console.log(typeof function () {} === 'function')
// true
Разницу между разными видами функций мы описали в статье «Функции».
typeof
СкопированоОператор typeof
возвращает не непосредственно «тип», а строку. Для всех примитивов, кроме null
, этой строкой будет название этого примитива.
Для объектов он сначала проверит, можно ли его «вызвать». Функции — это как раз такие объекты, поэтому оператор возвращает function
.
Несмотря на то, что typeof
не всегда возвращает то, что мы бы могли ожидать, им удобно пользоваться в некоторых случаях в коде, например, для определения функций.
Преобразование типов
СкопированоТеперь, когда мы разобрались с типами, посмотрим, как мы можем преобразовывать значения одного типа в значения другого.
В JavaScript существует лишь 3 типа конвертации: в строку, в число или в логическое значение.
Чтобы конвертировать значение в эти типы, можно воспользоваться одноимёнными функциями:
String(42) // Приводит к строке.Number('42') // Приводит к числу.Boolean(42) // Приводит к логическому значению.
String(42) // Приводит к строке.
Number('42') // Приводит к числу.
Boolean(42) // Приводит к логическому значению.
Приведение к строке, числу и логическому значению можно проводить над любыми значениями:
// К строке:String(123) // '123'String(-12.3) // '-12.3'String(null) // 'null'String(undefined) // 'undefined'String(true) // 'true'String(false) // 'false'String(function () {}) // 'function () {}'String({}) // '[object Object]'String({ key: 42 }) // '[object Object]'String([]) // ''String([1, 2]) // '1,2'
// К строке:
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
String(function () {}) // 'function () {}'
String({}) // '[object Object]'
String({ key: 42 }) // '[object Object]'
String([]) // ''
String([1, 2]) // '1,2'
К числу также можно пытаться приводить любые значения. Если JavaScript не сможет привести какое-то значение к числу, мы получим NaN
— особое значение, представляющее не-число (Not-a-Number).
// К числу:Number('123') // 123Number('123.4') // 123.4Number('123,4') // NaNNumber('') // 0Number(null) // 0Number(undefined) // NaNNumber(true) // 1Number(false) // 0Number(function () {}) // NaNNumber({}) // NaNNumber([]) // 0Number([1]) // 1Number([1, 2]) // NaN// Обратите внимание, что Number от пустого массива — 0,// от массива с одним числом — это число// и от массива с несколькими числами — NaN.// Почему так происходит, мы поймём чуть ниже.
// К числу:
Number('123') // 123
Number('123.4') // 123.4
Number('123,4') // NaN
Number('') // 0
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(function () {}) // NaN
Number({}) // NaN
Number([]) // 0
Number([1]) // 1
Number([1, 2]) // NaN
// Обратите внимание, что Number от пустого массива — 0,
// от массива с одним числом — это число
// и от массива с несколькими числами — NaN.
// Почему так происходит, мы поймём чуть ниже.
К логическому также можно приводить любые значения:
Boolean('') // falseBoolean('string') // trueBoolean('false') // trueBoolean(0) // falseBoolean(42) // trueBoolean(-42) // trueBoolean(NaN) // falseBoolean(null) // falseBoolean(undefined) // falseBoolean(function () {}) // trueBoolean({}) // trueBoolean({ key: 42 }) // trueBoolean([]) // trueBoolean([1, 2]) // true// Грубо говоря, всё, кроме пустой строки, нуля,// NaN, null и undefined — true.
Boolean('') // false
Boolean('string') // true
Boolean('false') // true
Boolean(0) // false
Boolean(42) // true
Boolean(-42) // true
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(function () {}) // true
Boolean({}) // true
Boolean({ key: 42 }) // true
Boolean([]) // true
Boolean([1, 2]) // true
// Грубо говоря, всё, кроме пустой строки, нуля,
// NaN, null и undefined — true.
Неявное преобразование типов
СкопированоВ секции выше мы преобразовывали типы «руками», с помощью функций. Но JavaScript может делать такие преобразования за нас самостоятельно. (Из-за чего в языке появляется много странностей, за которые его не очень сильно любят.)
Такая типизация, при которой тип значения определяется во время присвоения, а по ходу программы может меняться, — называется динамической.
Неявное преобразование происходит, когда мы заставляем JavaScript работать со значениями разных типов. Например, если мы хотим «сложить» число и строку:
5 + '3' === '53'5 - '3' === 25 + '-3' === '5-3'5 - +3 === 25 + -3 === 2// Из-за этого же появилась и такая шутка:Array(16). join('wat' - 1) + ' Batman!'// 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
5 + '3' === '53'
5 - '3' === 2
5 + '-3' === '5-3'
5 - +3 === 2
5 + -3 === 2
// Из-за этого же появилась и такая шутка:
Array(16).join('wat' - 1) + ' Batman!'
// 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
Дело в том, как JavaScript пробует эти два типа «сопоставить» друг с другом, чтобы с ними работать.
Вначале посмотрим на примитивы.
- Интерпретатор приведёт примитивные значения к логическим, если мы используем
&&
или||
. - К строке, если мы используем
+
, когда один из операндов — строка. - К числу, если:
- мы используем операторы сравнения
<
,<
,= >
,>
;= - используем арифметические операции
-
,+
(за исключением пункта 2),/
,*
. - используем унарный плюс:
+'2'
;= = = 2 - используем оператор нестрогого сравнения
=
.=
- мы используем операторы сравнения
Но примитивами дело не заканчивается, JavaScript также неявно приводит и не примитивные значения.
Интерпретатор приводит их к логическому, если мы используем &&
или ||
. Объекты — всегда true
.
С числом и строкой всё немного интереснее. Чтобы определить, к строке приводить значение или к числу, JavaScript смотрит, какой из двух методов (value
и to
) в текущем объекте объявлен.
- Если перед нами не объект
Date
, то методvalue
вызывается, обычно, первым (если не сильно углубляться в детали спецификации).Of ( ) - Если возвращённое после этого значение — это примитив, то возвращается оно.
- Если нет, то вызывается другой метод (если
value
не вернул примитив, то вызываетсяOf ( ) to
и наоборот).String ( ) - Если после этого вернулся примитив, возвращается он.
- Если даже после этого не вернулся примитив, то будет ошибка
Uncaught TypeError
.: Cannot convert object to primitive value
На примерах
Скопировано// 1. Простой объектconst obj1 = {}obj1.valueOf() // {}obj1.toString() // '[object Object]'// Чтобы «сложить» число с объектом,// вначале будет вызван obj1.valueOf().// Он вернёт объект (непримитив),// после чего будет вызван obj1.toString().1 + obj1// 1 + '[object Object]'// '1' + '[object Object]'// '1[object Object]'// 2. Объект с указанным .valueOf()const obj2 = {}obj2.valueOf = () => 'obj2'obj2. valueOf() // 'obj2'obj2.toString() // '[object Object]'// Теперь, когда мы объявили метод .valueOf(),// при вызове он будет возвращать строку.// Так как строка — примитив,// она и будет использована при «сложении».1 + obj2// 1 + 'obj2'// '1' + 'obj2'// '1obj2'// 2.1. Если же мы будем возвращать числоconst obj2 = {}obj2.valueOf = () => 42obj2.valueOf() // 42obj2.toString() // '[object Object]'1 + obj2// 1 + 42// 43// 3. Датыconst date = new Date()date.valueOf() // 1467864738527date.toString() // 'Sun Sep 15 2019...'// У дат приоритет методов обратный:// то есть вначале будет вызываться .toString(),// и только после него — .valueOf().1 + date// 1 + 'Sun Sep 15 2019...'// '1' + 'Sun Sep 15 2019...'// '1Sun Sep 15 2019...'
// 1. Простой объект
const obj1 = {}
obj1.valueOf() // {}
obj1.toString() // '[object Object]'
// Чтобы «сложить» число с объектом,
// вначале будет вызван obj1.valueOf().
// Он вернёт объект (непримитив),
// после чего будет вызван obj1.toString().
1 + obj1
// 1 + '[object Object]'
// '1' + '[object Object]'
// '1[object Object]'
// 2. Объект с указанным .valueOf()
const obj2 = {}
obj2.valueOf = () => 'obj2'
obj2.valueOf() // 'obj2'
obj2.toString() // '[object Object]'
// Теперь, когда мы объявили метод .valueOf(),
// при вызове он будет возвращать строку.
// Так как строка — примитив,
// она и будет использована при «сложении».
1 + obj2
// 1 + 'obj2'
// '1' + 'obj2'
// '1obj2'
// 2.1. Если же мы будем возвращать число
const obj2 = {}
obj2.valueOf = () => 42
obj2.valueOf() // 42
obj2.toString() // '[object Object]'
1 + obj2
// 1 + 42
// 43
// 3. Даты
const date = new Date()
date.valueOf() // 1467864738527
date.toString() // 'Sun Sep 15 2019...'
// У дат приоритет методов обратный:
// то есть вначале будет вызываться .toString(),
// и только после него — .valueOf().
1 + date
// 1 + 'Sun Sep 15 2019...'
// '1' + 'Sun Sep 15 2019...'
// '1Sun Sep 15 2019...'
Строгое и нестрогое равенство
СкопированоНеявное преобразование также используется, когда мы сравниваем значения через нестрогое равенство =
.
В отличие от строгого равенства (=
), в нём интерпретатор пробует привести типы к одному, чтобы сравнить.
Полный алгоритм сложный. Для удобства его свели в большую матрицу, которая показывает, «что чему равно» при строгом и нестрогом равенстве.
Вот таблица нестрогого равенства (зелёным отмечены значения, которые «равны»):
А вот — для строгого:
Хорошей практикой считается использовать только строгое сравнение, чтобы избежать неявного преобразования типов при сравнении.
На практике
СкопированоСаша Беспоясов советует
СкопированоВсегда используйте строгое равенство при сравнении значений.
🛠 Для удобства проверку на существование объекта можно проводить через if
, потому что объекты всегда приводятся к true
.
const exists = {}if (exists) { /* эта ветка выполнится */}const doesntExist = undefinedif (doesntExist) { /* эта ветка не выполнится */}
const exists = {}
if (exists) {
/* эта ветка выполнится */
}
const doesntExist = undefined
if (doesntExist) {
/* эта ветка не выполнится */
}
🛠 Если хочется описать сложную структуру, которая бы умела «вести себя», как число или строка, можно описать методы . value
или .to
.
const ticketPrice = { amount: 20, currency: 'USD', valueOf: () => 20, toString: () => '$20',}1 + ticketPrice // 1 + 20 -> 21console.log(ticketPrice)// $20
const ticketPrice = {
amount: 20,
currency: 'USD',
valueOf: () => 20,
toString: () => '$20',
}
1 + ticketPrice // 1 + 20 -> 21
console.log(ticketPrice)
// $20
На собеседовании
Скопировано Задать вопрос в рубрику❓
Чему будет равно значение переменной variable
: let variable
?
Федя Петраков отвечает
СкопированоЗначение переменной variable будет равно ‘115’. В переменной будет храниться строка, а не число.
let variable = 1 + '15'console.log(variable) // '115'
let variable = 1 + '15'
console. log(variable) // '115'
Оператор + в JavaScript используется для сложения чисел или конкатенации строк. В результате применения этого оператора к двум значениям получится строка или число. Если один из операндов будет строковым типом, то оба операнда сначала будут приведены к строке, а результатом станет их конкатенация.
Преобразование типов в JavaScript | by Serj Bulavyk
Know your engines!
10 min read·
Feb 4, 2018Перевод статьи Alexey Samoshkin “JavaScript type coercion explained”.
Преобразование типов это процесс конвертации значения из одного типа в другой (как например, строки в число, объекта к булевому значению и т. д.). Любой тип, будь то примитив или объект, может быть преобразован в другой тип. Для справки, примитивы это: number
, string
, boolean
, null
, undefined
+ Symbol
(добавлен в ES6).
В качестве примера преобразования типов, можно ознакомиться с JavaScript Comparison Table, где продемонстрировано как ведёт себя оператор нестрогого сравнения ==
для разных типов a
и b
.
Из-за побочного эффекта оператора ==
в виде неявного приведения типа, эта матрица выглядит довольно пугающей, и запомнить все эти комбинации не представляется возможным. К счастью, вам не обязательно это делать, достаточно просто знать принципы, которые лежат в основе преобразования типов.
Эта статья подробно расскажет вам о том, как работает механизм преобразования типов в JavaScript и вооружит вас необходимыми знаниями для того, что-бы вы могли самостоятельно объяснить как вычисляются и каков будет результат следующих выражений. В конце статьи я продемонстрирую ответы и объясню их.
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0
Этот список скорее представляет собой те вещи, которые вам как программисту делать не стоит, а поэтому расценивайте этот список как своеобразные упражнения, для того чтобы оценить насколько хорошо вы знаете механизм приведения типов. Если вам станет скучно, вы можете найти больше примеров на wtfjs.com.
Кстати, иногда вы можете встретить подобные вопросы на собеседованиях на позицию JavaScript разработчика. Итак, поехали дальше 🙂
Явное и неявное преобразование
Преобразование типов может происходить явно и неявно.
Когда разработчик хочет намеренно произвести преобразование типов, написав, к примеру Number(value)
, это называется явным преобразованием типов (или type casting).
Так как JavaScript это слабо типизированный язык, преобразование между разными типами может происходить автоматически, и это называется неявным преобразованием типов. Чаще всего это происходит когда вы применяете операторы к значениям разных типов, таких как 1 == null
, 2 / `5`
, null + new Date()
, может происходить в зависимости от контекста, как например, в случае с if (value) {…}
, где value будет приведено к булевому значению.
Оператор строгого равенства ===
не приводит к неявному преобразованию типов. Оператор нестрогого равенства ==
, в свою очередь, производит сравнение операндов и, если требуется, неявное преобразование типов.
Неявное преобразование типов — это палка о двух концах: с одной стороны это источник проблем и разочарований, а с другой — механизм, который позволяет нам писать меньше кода, не теряя при этом читабельности.
Три типа конвертации
Во-первых, следует знать, что в JavaScript существует всего 3 типа преобразования:
- строковое
- булевое
- численное
Во-вторых, логика преобразования для примитивов и объектов работает по-разному, но, и примитивы и объекты могут быть преобразованы только этими тремя способами.
Давайте сначала разберёмся с примитивами.
Приведение к строке
Для явного приведения значения к строке необходимо применить к нему функцию String()
. Неявное преобразование будет вызвано бинарным оператором +
, кода один из операндов является строкой:
String(123) // explicit
123 + '' // implicit
Все примитивы будут приведены к строке вполне естественно, как вы могли и ожидать:
String(123) // '123'
String(-12. 3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
Преобразование символов происходит немного сложнее, потому что они могут быть преобразованы только явным образом. Вы можете прочитать об этих правилах подробнее здесь.
String(Symbol('my symbol')) // 'Symbol(my symbol)'
'' + Symbol('my symbol') // TypeError is thrown
Булевое преобразование
Для явного преобразования к булевому значению, нужно применить функцию Boolean()
. Неявное преобразование происходит в логическом контексте if (val) { … }
или при применении логических операторов (||
&&
!
).
Boolean(2) // explicit
if (2) { ... } // implicit due to logical context
!!2 // implicit due to logical operator
2 || 'hello' // implicit due to logical operator
На заметку, логические операторы такие как ||
и &&
производят булевое преобразование под капотом, но при этом всегда возвращают оригинальное значение операндов, даже если они не являются булевыми.
// returns number 123, instead of returning true
// 'hello' and 123 are still coerced to boolean internally to calculate the expression
let x = 'hello' && 123; // x === 123
Поскольку существует всего два возможных результата преобразования, легче просто запомнить список ложных значений:
Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
Любое значение, которое не вошло в этот список, будет преобразовано в true
, включая объекты, функции, Array
, Date
и так далее. Символы, пустые объекты и массивы так же будут иметь значение true
.
Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
Численное преобразование
Для явного преобразования к числу нужно применить функцию Number()
, точно так же, как мы делали с Boolean()
и String()
. ~
)
-
+
*
/
%
). Обратите внимание, что бинарный оператор +
не вызывает численного преобразования, если один из операндов является строкой+
==
(включая !=
). Обратите внимание, что данный оператор не вызывает численное преобразование, если оба операнда являются строкамиNumber('123') // explicit
+'123' // implicit
123 != '456' // implicit
4 > '5' // implicit
5/null // implicit
true | 0 // implicit
Примеры того, как примитивы будут преобразованы в числа:
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12. 34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
При преобразовании строки в число, движок сначала отсекает все пробельные символы, символы \n
, и \t
в начале и в конце строки, и возвращает NaN
если обрезанная строка не представляет из себя корректное число. Если строка окажется пустой, то результатом будет 0
.
null
и undefined
обрабатываются по разному: null
станет 0
, в то время как undefined
станет NaN
.
Численное преобразование, как явное так и неявное, не работает для символов. Более того, движок бросает ошибку TypeError
, вместо того, чтобы по-тихому преобразовать Symbol
в NaN
, как это происходит с undefined
. Подробнее о правилах преобразования символов можно посмотреть на MDN.
Number(Symbol('my symbol')) // TypeError is thrown
+Symbol('123') // TypeError is thrown
Существует два специальных правила которые следует запомнить:
- При применении
==
кnull
илиundefined
, численное преобразование не происходит, так какnull
может равняться толькоnull
илиundefined
, и ничему другому.
null == 0 // false, null is not converted to 0
null == null // true
undefined == undefined // true
null == undefined // true
2. NaN
не равен ничему, даже самому себе.
if (value !== value) { console.log("we're dealing with NaN here") }
Преобразование типов для объектов
До текущего момента мы рассматривали преобразования для примитивов, что является достаточно банальным.
Когда дело доходит до объектов, и движок встречает выражение вроде [1] + [2, 3]
, ему сначала необходимо привести объекты к примитивным значениям, а уже потом выполнить финальное преобразование. Как и в случае с примитивами, объект может быть преобразован всего тремя способами: численным, строковым, булевым.
Самый простой пример это булевое преобразование — любое не примитивное значение всегда приводится к true
, включая пустые объекты и массивы.
Объекты приводятся к примитивам посредством вызова внутреннего метода [[ToPrimitive]]
, который отвечает как за численное, так и за строковое преобразование.
Вот псевдокод реализации метода [[ToPrimitive ]]
:
Методу [[ToPrimitive]]
передаётся два аргумента: входящее значение и предпочтительный тип для преобразования: Number
или String
. Второй аргумент является опциональным.
Как для строкового так и для численного преобразования используются два метода объекта: valueOf
и toString
. Оба метода объявлены в Object.prototype
, а значит доступны для всех производных типов, таких как Date
, Array
и т.д.
В общих чертах алгоритм выглядит следующим образом:
- Если входящее значение уже является примитивом, ничего не делать и просто вернуть его.
- Вызвать
input.toString()
, если результат примитив — вернуть его. - Вызвать
input.valueOf()
, если результат примитив — вернуть его. - Если ни один из методов не вернул примитив — бросить ошибку
TypeError
.
При численном преобразовании сначала вызывается метод valueOf()
, а уже затем toString()
. При строковом преобразовании наоборот — сначала происходит вызов toString()
, а уже потом valueOf()
.
Большинство встроенных типов не имеют метода valueOf
или же имеют valueOf
, который возвращает свой собственный объект this
, который игнорируется, так как this
не является примитивом. Вот почему численное и строковое преобразование в большинстве случаев работает одинаково — оба в конечном итоге вызывают метод toString()
.
Разные операторы могут вызывают строковое или численное преобразование при помощи параметра preferredType
. Но существует два исключения: нестрогое равенство ==
и бинарный оператор +
, которые вызывают режим преобразования по умолчанию (preferredType
не указан или равен default
). В таком случае, большинство встроенных типов подразумевают численное преобразование, за исключением Date
, который предпочитает строковое преобразование.
Вот пример преобразования Date
:
Вы можете переопределить методы toString()
и valueOf()
для того, чтобы повлиять на логику преобразования объектов в примитив.
Обратите внимание, как obj + ‘’
возвращает строку 101
. Оператор +
вызывает преобразование в режиме по умолчанию, и как упоминалось выше, Object
подразумевает численное преобразование в таком случае, используя сначала метод valueOf()
, а затем уже toString()
.
Метод ES6 Symbol.toPrimitive
В ES5 вы можете повлиять на логику преобразования объекта в примитив, переопределив методы toString()
и valueOf()
.
В ES6 вы можете пойти дальше и полностью заменить внутреннюю процедуру метода [[ToPrimitive]]
, реализовав метод [Symbol.toPrimitive]()
у объекта.
Примеры
Вооружившись теорией, давайте вернёмся к нашим примерам:
true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1] > null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!!"false" == !!"true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
[1,2,3] == [1,2,3] // false
{}+[]+{}+[1] // '0[object Object]1'
!+[]+[]+![] // 'truefalse'
new Date(0) - 0 // 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0'
Ниже представлен детальный разбор того, как вычисляется каждое из выражений.
Бинарный оператор +
вызывает численное преобразование для true
и false
:
true + false
==> 1 + 0
==> 1
Оператор деления /
вызывает численное преобразование строки 6
:
12 / '6'
==> 12 / 6
==>> 2
Оператор +
выполняется слева направо, поэтому сначала выполнится выражение “number” + 15
. Поскольку один из операндов это строка, оператор +
вызовет строковое преобразование числа 15
и последующую конкатенацию двух строк. На следующем этапе выражение “number15” + 3
выполнится таким же образом.
“number” + 15 + 3
==> "number15" + 3
==> "number153"
Сначала выполняется сложение чисел 15 + 3
. На данном этапе никакого преобразования не нужно, так как оба операнда являются числами. Затем выполняется выражение 18 + ‘number’
, и так как один из операндов является строкой, то вызывается строковое преобразование для числа 18
, и последующая конкатенация двух строк.
15 + 3 + "number"
==> 18 + "number"
==> "18number"
Оператор сравнения >
вызывает численное преобразование для [1]
и null
[1] > null
==> '1' > 0
==> 1 > 0
==> true
Унарный оператор +
имеет более высокий приоритет чем бинарный оператор +
. Поэтому выражение + 'bar'
выполняется первым. Унарный плюс вызывает численное преобразования строки bar
. Так как эта строка не представляет собой корректное число, результатом будет NaN
. Следующим шагом выполнится выражение 'foo' + NaN
.
"foo" + + "bar"
==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
Оператор сравнения ==
вызывает численное преобразование, поэтому строка true
конвертируется в NaN
, а правый операнд true
станет 1
.
'true' == true
==> NaN == 1
==> falsefalse == 'false'
==> 0 == NaN
==> false
Оператор ==
обычно вызывает численное преобразование, но не в случае с null
. Действует исключение из правил: null
равен только null
или undefined
и ничему другому.
null == ''
==> false
Оператор !!
конвертирует строки true
и false
в булевое значение true
, так как это не пустые строки. А дальше оператор ==
просто сравнивает два булевых значения безо всяких преобразований.
!!"false" == !!"true"
==> true == true
==> true
Оператор ==
вызывает численное преобразование для массива. Метод массива valueOf()
возвращает сам массив, а значит результат игнорируется, так как не является примитивом. Далее, вызывается метод массива toString()
, который конвертирует [‘x’]
в строку ‘x’
.
['x'] == 'x'
==> 'x' == 'x'
==> true
Оператор +
вызывает численное преобразование массива. Метод массива valueOf()
вернёт сам массив, поэтому результат игнорируется, поскольку не является примитивом.
Далее выполняется выражение ’’ + null + 1
.
[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'
Логические операторы ||
и &&
преобразовывают операнды к булевому значению, но всегда возвращают оригинальное значение операнда (не булевое). 0
станет false
, а поскольку 0
является не пустой строкой, то конвертируется в true
. {}
пустой объект тоже становится true
.
0 || "0" && {}
==> (0 || "0") && {}
==> (false || true) && true // internally
==> "0" && {}
==> true && true // internally
==> {}
В данном примере никакого преобразования не требуется, потому что оба операнда одного типа. Так как оператор ==
сравнивает объекты по ссылке, а не по значению, а данные массивы являются двумя разными экземплярами, результатом будет false
.
[1,2,3] == [1,2,3]
==> false
Все операнды являются не примитивами, поэтому +
вызывает численное преобразование. Методы Object.valueOf()
и Array.valueOf()
возвращают самих себя, соответственно будут проигнорированы. В качестве запасного варианта, вызывается метод toString()
. Трюк в том, что первый {}
воспринимается движком не как создание объекта, а как объявление пустого блока кода и поэтому игнорируется. Выполнение начинается с выражения +[]
, которое преобразуется в пустую строку посредством метода toString()
, и далее в 0
.
{}+[]+{}+[1]
==> +[]+{}+[1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'
Данный пример лучше объяснить пошагово с точки зрения приоритета выполнения операторов:
!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
Оператор -
вызывает численное преобразование для объекта Date
. Date.valueOf()
возвращает количество миллисекунд прошедших с начала Unix эпохи (в данном случае 0
).
new Date(0) - 0
==> 0 - 0
==> 0
Оператор +
вызывает преобразование по умолчанию. Date
, как исключение, подразумевает строковое преобразование, поэтому используется метод toString()
, а не valueOf()
.
new Date(0) + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
Источники
Я бы хотел порекомендовать отличную книгу “Understanding ES6” написанную Nicholas C. Zakascholas. Книга является отличным ресурсом для изучения ES6 с достаточным уровнем освещения: не слишком поверхностно, и в тоже время не погружается в механику работы движка черезчур.
Ещё одна отличная книга, на этот раз по ES5 — SpeakingJS написанная Axel Rauschmayer.
Современный учебник Javascript — https://learn.javascript.ru/,
в особенности эти две страницы, посвящённые преобразованию типов.
JavaScript Comparison Table — https://dorey.github.io/JavaScript-Equality-Table/
wtfjs — небольшой блог о языке, который мы все так любим, не смотря на то что он дает много поводов его ненавидеть — https://wtfjs.com/
Преобразование типов Python (с примерами)
В этом руководстве мы узнаем о преобразовании типов Python с помощью примеров.
В программировании преобразование типов — это процесс преобразования данных одного типа в другой. Например: преобразование данных int
в str
.
В Python существует два типа преобразования типов.
- Неявное преобразование — автоматическое преобразование типов
- Явное преобразование — ручное преобразование типов
Неявное преобразование типов Python
В определенных ситуациях Python автоматически преобразует один тип данных в другой. Это известно как неявное преобразование типов.
Пример 1: Преобразование целого числа в число с плавающей запятой
Давайте рассмотрим пример, в котором Python поддерживает преобразование младшего типа данных (целое) в более высокий тип данных (плавающее), чтобы избежать потери данных.
целое_число = 123 float_number = 1,23 новое_число = целое_число + плавающее_число # отображаем новое значение и результирующий тип данных print("Значение:",new_number) print("Тип данных:",type(new_number))
Выход
Значение: 124,23 Тип данных:
В приведенном выше примере мы создали две переменные: integer_number и float_number типа int
и float
соответственно.
Затем мы добавили эти две переменные и сохранили результат в new_number .
Как мы видим, new_number имеет значение 124,23 и имеет число с плавающей точкой 9.0006 тип данных.
Это связано с тем, что Python всегда преобразует меньшие типы данных в большие, чтобы избежать потери данных.
Примечание:
- Мы получим
TypeError
, если попытаемся добавитьstr
иint
. Например,'12' + 23
. Python не может использовать неявное преобразование в таких условиях. - В Python есть решение для подобных ситуаций, известное как явное преобразование.
Явное преобразование типов
При явном преобразовании типов пользователи преобразуют тип данных объекта в требуемый тип данных.
Мы используем встроенные функции, такие как int()
, float()
, str()
и т. д. для выполнения явного преобразования типов.
Этот тип преобразования также называется приведением типов, поскольку пользователь приводит (изменяет) тип данных объектов.
Пример 2. Добавление строки и целого числа с использованием явного преобразования
num_string = '12' число_целое = 23 print("Тип данных num_string до приведения типов:",type(num_string)) # явное преобразование типов num_string = целое (num_string) print("Тип данных num_string после приведения типов:",type(num_string)) num_sum = num_integer + num_string печать("Сумма:",num_sum) print("Тип данных num_sum:",type(num_sum))
Вывод
Тип данных num_string до приведения типов:Тип данных num_string после приведения типов: Сумма: 35 Тип данных num_sum: <класс 'int'>
В приведенном выше примере мы создали две переменные: num_string и num_integer со значениями типа str
и int
соответственно. Обратите внимание на код:
num_string = int(num_string)
Здесь мы использовали int()
для выполнения явного преобразования типа num_string в целочисленный тип.
После преобразования num_string в целочисленное значение Python может добавить эти две переменные.
Наконец, мы получили значение num_sum , т.е. 35 и тип данных int
.
Ключевые моменты, которые следует помнить
- Преобразование типов — это преобразование объекта из одного типа данных в другой тип данных.
- Неявное преобразование типов автоматически выполняется интерпретатором Python.
- Python позволяет избежать потери данных при неявном преобразовании типов.
- Явное преобразование типов также называется преобразованием типов, типы данных объектов преобразуются с использованием предопределенных функций пользователем.
- При приведении типов может произойти потеря данных, поскольку мы привязываем объект к определенному типу данных.
Содержание
Преобразование типов в Java | Два основных типа преобразования в Java
Как только переменные и константы различных типов будут объединены в выражение, их можно преобразовать в аналогичный тип. Этот метод преобразования одного предопределенного типа в другой известен как преобразование типов в Java.
Преобразование типа
В языках программирования мы используем два разных типа преобразования.
1. Неявное преобразование типов
Если преобразование типов выполняется мгновенно через компилятор без участия программиста, преобразование типов называется неявным преобразованием типов. Компилятор принципиально поощряет каждый операнд к типу данных наибольшего операнда. Во время преобразования данных не происходит потери данных. Нет возможности выдать исключение при преобразовании, и поэтому он известен как типобезопасный. Преобразование количества меньшего размера в слишком большое число может быть неявным преобразованием. Преобразование данных целочисленного типа в число с плавающей запятой.
число с плавающей точкой i=0; интервал j=10; я=j;
// Это может быть неявное преобразование, поскольку число с плавающей запятой может быть больше, чем целое число, поэтому недостатка в данных нет, а также нет исключений.
2. Явное преобразование типов
Преобразование типов, которое может быть осуществлено с помощью программиста, называется явным преобразованием типов. по существу, программист делает выражение, чтобы оно стало определенным типом. Явное преобразование типов может быть известно как приведение типов. Потеря данных может произойти или не произойти во время преобразования данных. Поэтому существует вероятность потери деталей. он может выдать ошибку, если, возможно, попытается выполнить без приведения типов. Преобразование большего числа в число меньшего размера может быть явным преобразованием.
с плавающей запятой k=123,456 int i= (int) k
// это может быть явным преобразованием, а также (int) оператором приведения типов. На этом этапе нам может удастся избежать исключения, но вы можете обнаружить видимую потерю данных. i=123
// .456 можно отбросить в процессе преобразования
Преобразование типов в Java
Как и в других языках программирования, в Java существует 2 типа преобразования:
Неявное преобразование типов
- Обычно это то, что мы называем расширенным преобразованием, и это можно сделать автоматически, потому что мы переходим к более широким типам данных. Итак, если у нас есть 32-битное целое число, и мы хотим перейти к 64-битному целому, это шире. Таким образом, значение можно безопасно перемещать, чтобы это можно было сделать неявно. Компилятор должен решить, как сделать эти преобразования, и правила довольно просты.
- Если у нас есть выражение с несколькими (смешанными) целочисленными размерами в нем, если у нас есть короткие и длинные, независимо от того, какой самый большой целочисленный размер, он будет преобразован. Итак, если мы проделаем операцию с коротким и длинным, то короткое будет неявно преобразовано в вместе.
- Если мы выполняем операцию со смешанными размерами с плавающей запятой, то есть у нас есть число с плавающей запятой и число с двойной точностью, они всегда будут переходить к двойному, потому что двойное число является наибольшим размером с плавающей запятой.
- Затем, если у нас есть операция, в которой используются смешанные целочисленные типы и типы с плавающей запятой, компилятор выполнит приведение к наибольшей плавающей запятой в уравнении. Итак, если мы проделаем операцию с лонгом и на плаву, лонг будет брошен на плаву.
- Если мы проделаем операцию с long и double, то long будет преобразовано в double.
Явное преобразование типов
- Мы явно выполняем в нашем коде при использовании этой операции приведения. Делая это, мы берем на себя ответственность за все, что происходит в результате такого обращения. Благодаря этому мы можем выполнять как расширяющее, так и узкое преобразование. Он расширяется с 32-битного до 64-битного и сужается с 64-битного до 32-битного. Мы должны просто хотеть знать, что мы знаем, что потенциально может произойти.
- Если мы выполним явное приведение от числа с плавающей запятой к целому числу, то плавающие точки могут иметь дробную часть, а целое число — нет, поэтому любая дробная часть будет отброшена, когда мы приведем это число с плавающей запятой к целому числу.
- Вы хотите быть осторожным при выполнении сужающего преобразования. Если у нас есть 64-битное целое число, оно может содержать значения, которые слишком велики, чтобы поместиться в 32-битное целое число.
- Итак, если мы приведем это 64-битное значение к 32-битному, программа на самом деле сделает это, но если значение слишком велико для 32-битного, вы получите некоторые странные результаты. Таким образом, вы хотите убедиться, что, выполняя сужающий заброс, вы знаете, что то, что вы делаете, безопасно.
- И последнее: вы должны быть осторожны при преобразовании из целого числа в число с плавающей запятой, потому что, если у вас есть целое число с большим количеством значащих цифр, из-за способа хранения числа с плавающей запятой, вы можете потерять некоторые этих значащих цифр.
Примеры преобразования типов
Примеры преобразования типов, подробно описанные ниже:
Код:
У нас есть простая программа, некоторое объявление вверху, float, double, byte, short и long , и каждая переменная названа, чтобы помочь определить, каковы их типы, например, float is floatVal,
Код:
long is longVal
Код:
И программа просто выводит сообщение об успехе, если она запускается.
Итак, давайте просто посмотрим, как здесь работает преобразование типов. Итак, давайте прежде всего продолжим и создадим переменную. У нас есть шорт, и мы просто назовем его результатом.
На самом деле, давайте назовем это результатом1. И давайте просто выполним простое задание. Итак, сначала мы просто назначим ему byteVal. Теперь, как мы и ожидали, если мы продолжим и запустим это, то запустим успешно.
Код:
Вывод:
Мы знаем, что это допустимое присваивание, потому что байт можно присвоить короткому, потому что это расширяющее преобразование.
Если мы возьмем этот byteVal и сделаем вместо него longVal, то теперь он будет на самом деле длинным; если мы запустим это, мы получим сообщение об ошибке, говорящее о несовместимом типе, возможная потеря преобразования из длинного в короткое.
Код:
Итак, что мы можем здесь сделать, так это выполнить явное приведение типов. Мы просто будем сокращать перед этим. Итак, теперь он действителен, поэтому мы можем запустить его.
Код:
И, конечно же, это работает. Потому что длинная позиция не могла перейти в короткую, потому что это была сужающая конверсия.
Вывод:
Но если поставить перед ним явное приведение, то теперь оно допустимо. Если мы хотим, мы можем сделать нотацию приведения очень явной и сказать, что вы знаете, мы знаем, хотя преобразование байтов допустимо, мы хотим явно показать, что мы приводим его, помещая туда короткое приведение, мы можем сделать это, и это совершенно законно.
Код:
Вывод:
Теперь давайте рассмотрим другой сценарий. Мы собираемся создать еще одну переменную, которую назовем result2 , , и result2 тоже будет коротким. И что мы хотим здесь сделать, так это просто взять наш byteVal и вычесть из него longVal. Теперь мы знаем, что это недопустимо, потому что результатом выражения будет размер наибольшего целого числа в нем, то есть длины.
Код:
Итак, если мы запустим это, мы получим сообщение об ошибке, говорящее о том, что невозможно преобразовать длинное в короткое.
Но предположим, что мы хотим продолжить и сохранить этот результат как можно короче. Нам нужно сделать слепок. Но мы хотим использовать это время как значение всего результата здесь. Итак, что мы собираемся сделать, так это поставить короткий состав перед ним.
Поместите короткий слепок перед ним здесь. И заключить все это в скобки. И запустить его.
Код:
Он будет успешно запущен.
Вывод:
Теперь объявите еще одну переменную с именем result3, но объявите ее длинной. Итак, мы получили результат 3, и что мы будем делать здесь, так это назначать наш longVal — floatVal. Итак, мы запускаем это, ошибка теряет преобразование из float в long, потому что всякий раз, когда у нас есть целочисленный тип и любой тип с плавающей запятой, результатом будет тип с плавающей запятой.
Код:
Итак, давайте продолжим и преобразуем наш результат в число с плавающей запятой. Итак, сделав это плавающим, мы должны иметь возможность продолжить и запустить его. И успешно бежать.
Код:
Вывод:
Но теперь, если мы возьмем floatVal здесь и преобразуем его в doubleVal, и если мы попытаемся запустить это, мы получим ошибку потому что это говорит, что результат будет двойным, потому что, когда вы делаете целое число и число с плавающей запятой, это размер наибольшего числа с плавающей запятой в уравнении.
Код:
Итак, давайте сделаем этот результат двойным, так что теперь мы можем запустить его.
Код:
Вывод:
Заключение
1. Переменные строго типизированы в Java
2. Примитивные типы
- Целочисленные типы, типы с плавающей запятой, тип char, тип Boolean
3. Преобразование типов
- Нам часто приходится переключаться между разными типами, потому что по мере усложнения наших программ они, скорее всего, будут использовать несколько типов данных.
- Компилятор может обрабатывать расширяющиеся преобразования типов, переходя от одного типа к другому, который может содержать большие значения,
- Но вы также можете использовать приведение для явного выполнения тех типов преобразований, которые компилятор не может выполнять автоматически.
Рекомендуемые статьи
Это руководство по преобразованию типов в Java.