Кратко
СкопированоПредставим ситуацию: у нас есть форма с полем, в которое пользователь вписывает свой возраст в годах.
По умолчанию любой ввод в полях — это строка. Если мы хотим работать с этим значением, как с числом, то нам нужно привести его к числу.
Приведение (или преобразование) типов — это процесс конвертации значения из одного типа в другой.
В 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” — чуть позже.
Примитивные типы — это такие типы, значения которых можно только перезаписать, но нельзя изменить.
Например, если мы создали переменную со значением 42
, изменить это значение будет нельзя. Мы сможем его только полностью перезаписать:
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
На собеседовании
Скопировано отвечает
СкопированоОператоры =
и =
в JavaScript — это операторы сравнения, которые мы используем, чтобы определить, равны или нет два значения.
Двойное равно =
часто называют «нестрогим равенством», потому что этот оператор перед сравнением величин выполняет приведение типов:
const foo = 100;const bar = '100';console.log(foo == bar) // true
const foo = 100; const bar = '100'; console.log(foo == bar) // true
Оператор =
выполняет строгое сравнение, без приведения типов. При использовании тройного равно сравниваются и типы, и значения.
При сравнении переменных сначала проверяется, отличаются ли их типы.
Если типы совпадают, то проверяется значение. Если значения одинаковы, возвращается true.
const foo = 100;const bar = '100';console.log(foo === bar); // falseconst foo = '100';const bar = '100';console.log(foo === bar); // true
const foo = 100; const bar = '100'; console.log(foo === bar); // false const foo = '100'; const bar = '100'; console.log(foo === bar); // true
Преобразование типов в JavaScript иногда может приводить к неожиданным результатам, поэтому хорошей практикой считается использовать оператор строгого равенства =
при сравнении.
отвечает
СкопированоЗначение переменной variable будет равно '115'. В переменной будет храниться строка, а не число.
let variable = 1 + '15'console.log(variable) // '115'
let variable = 1 + '15' console.log(variable) // '115'
Оператор + в JavaScript используется для сложения чисел или конкатенации строк. В результате применения этого оператора к двум значениям получится строка или число. Если один из операндов будет строковым типом, то оба операнда сначала будут приведены к строке, а результатом станет их конкатенация.
отвечает
СкопированоОператор typeof
возвращает строку, представляющую тип данных операнда.
console.log(typeof 42); // "number"console.log(typeof "Hello"); // "string"console.log(typeof true); // "boolean"console.log(typeof undefined); // "undefined"console.log(typeof null); // "object"console.log(typeof {}); // "object"console.log(typeof []); // "object"console.log(typeof function(){});// "function"
console.log(typeof 42); // "number" console.log(typeof "Hello"); // "string" console.log(typeof true); // "boolean" console.log(typeof undefined); // "undefined" console.log(typeof null); // "object" console.log(typeof {}); // "object" console.log(typeof []); // "object" console.log(typeof function(){});// "function"
Но есть некоторые важные моменты, связанные с оператором typeof
:
-
Null: Одна из известных особенностей —
typeof null
возвращаетobject
. Это считается ошибкой в языке, но сохранено из-за обратной совместимости. -
Функции:
typeof
для функций возвращаетfunction
. Функции в JavaScript это объекты, но операторtypeof
позволяет их отличить от обычных объектов. -
Массивы:
typeof
возвращает[ ] object
, поскольку массивы в JavaScript являются разновидностями объектов. Для проверки, является ли переменная массивом, можно воспользоваться методом
Array.isArray. -
создание через конструктор:
typeof new
илиNumber ( 42 ) typeof new
возвращаетString ( 'строка' ) object
, т.к. когда используется ключевое слово new перед Number(42), создается новый объект типа Number, который является объектной оберткой вокруг числа 42. Таким образом, typeof new Number(42) возвращает "object", поскольку вы создали объект типа Number, даже если его внутреннее значение является числом.
Использование typeof
: часто применяется для проверки типов переменных в условиях, чтобы выполнить различные действия в зависимости от типа данных.
отвечает
СкопированоИз всех способов преобразования строки в число, использование унарного оператора +str
— самый быстрый, потому что не требует математических или других операций.
Можно проверить и сравнить результаты различных способов преобразования на MeasureThat.net.
отвечает
СкопированоЕсть несколько способов явного преобразования строки в число. Рассмотрим примеры:
const str = '42'// 1. Функция `Number()`const num1 = Number(str)// 2. Методы `Number.parseInt()` и `Number.parseFloat()`const num2 = Number.parseInt(str)const num3 = Number.parseFloat(str)// 3. Унарный плюс (оператор `+`)const num4 = +str// 4. Унарный минус (оператор `-`)const num5 = -str
const str = '42' // 1. Функция `Number()` const num1 = Number(str) // 2. Методы `Number.parseInt()` и `Number.parseFloat()` const num2 = Number.parseInt(str) const num3 = Number.parseFloat(str) // 3. Унарный плюс (оператор `+`) const num4 = +str // 4. Унарный минус (оператор `-`) const num5 = -str
Использование унарного плюса является самым быстрым способом преобразования строки в число, так как при этом не выполняются никакие дополнительные действия.
Единственный недостаток применения унарного плюса по сравнению с использованием функции Number
— работа со значениями типа Big
. При попытке преобразовать с помощью оператора +
строку с Big
-значением в число, будет брошена ошибка Type
.