Типы данных в JavaScript
В JavaScript определены семь встроенных типов данных. Шесть примитивных типов и один тип, который представляет из себя структуру данных:
-
boolean
- логический тип данных, который принимает значенияtrue
идиfalse
let val = false; val = true;
-
number
- числовой тип данных, который используется как для целых, так и для дробных чиселlet val = 50; val = 3.14159; val = -200;
Этот тип представляет из себя число двойной точности, подробнее о нём можно прочитать здесь.
Ноль в JavaScript имеет два представления:
-0
и+0
. («0» это синоним +0). На практике это имеет малозаметный эффект. Например, выражение+0 === -0
является истинным.А также есть специальные значения, которые по сути не являются числами, но принадлежат к числовому типу данных:
Infinity
(бесконечность) иNaN
(Not a Number - “Не Число”)let inf = Infinity; // прямое присвоение бесконечности inf = 57 / 0; // Infinity получится при делении на ноль inf = -Infinity; // есть отрицательная бесконечность inf = 57 / -0; // -Infinity let notNumber = NaN; notNumber = "строка" * 5; // При ошибке вычисления вернёт NaN
Для получения самого большого или самого меньшего доступного значения в пределах
+/-Infinity
, можно использовать константыNumber.MAX_VALUE
илиNumber.MIN_VALUE
. А начиная с ECMAScript 2015, вы также можете проверить, находится ли число в безопасном для целых чисел диапазоне, используя методNumber.isSafeInteger()
, либо константыNumber.MAX_SAFE_INTEGER
иNumber.MIN_SAFE_INTEGER
. За пределами этого диапазона операции с целыми числами будут небезопасными, и возвращать приближённые значения.Например, подумайте над следующей задачей. Какое значение должно быть у переменной
value
, чтобы после выполнению кода ниже, в консоли отобразились значения, указанные в комментариях?let value = ? console.log(i * i) // 0 console.log(i + 1) // 1 console.log(i - 1) // -1 console.log(i / i) // 1
Посмотреть ответ
```javascript let value = Number.MIN_VALUE; // имеет минимальное значение, практически равное нулю: 5e-324. Поэтому в первых трёх выражениях будет вести себя как ноль, а в последнем даст единицу. ``` Стоит заметить, что результатом деления на ноль `0/0` является `NaN`, поэтому использование нуля в этой задаче не дало бы необходимого результата. -
string
- тип данных строка. Строка представляет собой цепочку «элементов» 16-битных беззнаковых целочисленных значений. Каждый такой элемент занимает свою позицию в строке. Первый элемент имеет индекс 0, следующий — 1, и так далее. Длина строки — это количество элементов в ней. Строки в JavaScript могут быть как в двойных, так и в одинарных кавычках.let str = "Это строка"; console.log(str[0]); // Э - первый символ строки console.log(str.length); // 10 console.log(str[str.length - 1]); // а - последний символ строки str = 'А это "строка в двойных кавычках" внутри строки';
Также соединения строк (конкатенации) можно использовать оператор
+
или методString.concat()
. Также для составления строк с использованием значений переменных удобно применять шаблонизацию через обратные кавычки -`строка с переменной ${value}`
let name = "Василий"; let helloMsg = "Привет, " + name + "!"; // Привет, Василий! let newHelloMsg = name.concat(", здравствуйте. ", "Удачного дня!"); // Василий, здравствуйте. Удачного дня! //Для помещения значений переменных в шаблон используется конструкция ${...} helloMsg = `Привет, ${name}`; // Привет, Василий!
-
symbol
- это уникальный и неизменяемый тип данных, который может быть использован как идентификатор для свойств объектов. Чтобы создать новый символьный примитив, достаточно написать Symbol(), указав по желанию строку в качестве описания этого символа:let sym1 = Symbol(); let sym2 = Symbol("foo");
-
null
- этот тип данных имеет всего одно значение:null
. Которое выражает нулевое или «пустое» значение. Можно сказать, что оно передает смысл «нет значения»let data = null;
-
undefined
- тип данных, который так же имеет только одно соответствующее значение:undefined
. И отражает то, что данное значение «не определено». Например, если переменная была объявлена без присвоения ей какого-либо значение, то её значение будетundefined
.let data; console.log(data); // undefined
-
object
- тип данных Объект относится к структуре данных. Он содержит в себе данные и инструкции по работе с этими данными. Объект — это ссылочный тип данных, то есть это значение в памяти, на которое возможно сослаться с помощью идентификатора. Все другие структуры данных, такие как Функции (Functions), Массивы (Arrays), Коллекции (Maps, Sets, WeakMaps, WeakSets) и т.д. тоже являются объектами. Подробнее объекты и структуры данных будут рассматриваться в других частях курса.
Определение типов оператором typeof
Оператор typeof
возвращает строку, указывающую тип операнда. Синтаксис вызова этого оператора: typeof operand
. Например:
// Числа
console.log(typeof 42); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof NaN); // "number", несмотря на то, что смысл этого значения "Not-A-Number" (не число)
// Строки
console.log(typeof "строка"); // "string"
let name = "Василий";
console.log(typeof `Привет, ${name}`); // "string"
// Булевы значения
console.log(typeof true); // "boolean"
console.log(typeof false); // "boolean"
// Символы
console.log(typeof Symbol()); // "symbol"
// undefined
let declaredButUndefinedVariable;
console.log(typeof declaredButUndefinedVariable); // "undefined";
// Объекты
console.log(typeof { a: 1 }); // "object"
console.log(typeof [1, 2, 3]); // такая структура данных, как массив, тоже "object"
Но есть два значения, для которых оператор typeof
не совсем корректно отражает их тип:
-
Значение
null
, для негоtypeof
возвращает тип “object”, что является официально признанной ошибкой в языке, которая сохраняется для совместимости. На самом деле это не объект, а отдельный тип данныхnull
.// null console.log(typeof null); // "object"
-
Для функций оператор
typeof
возвращает тип “function”, хотя в действительности функция не является отдельным встроенным типом языка, и является подвидом объекта, как и другие структуры данных.// function console.log(typeof function() {}); // "function"
Примитивные и ссылочные типы данных
Особенность примитивных типов данных заключается в том, что они неизменяемы (иммутабельны) и передаются по значению. В отличие от объектов, которые передаются по ссылке. При этом важно понимать, что объект или примитив, это не сама переменная, а соответствующий указатель на объект или само значение примитивного типа, которое этой переменной присвоено.
Например рассмотрим следующий код:
// Примитивный тип number
let num = 5;
// Функция для прибавления двойки к примитиву
function addTwo(num) {
num = num + 2;
}
// Вызов функции
addTwo(num);
console.log(num); // 5
Что происходит после вызова этой функций?
В первую функцию addTwo
в качестве параметра передаётся значение переменной num
из глобальной области видимости, то есть 5
. Таким образом Запись Окружения этой функции после вызова будет выглядеть так:
let num = 5;
function addTwo(num) {
// на этапе создания контекста - {num: 5}, где num, это локальная переменная функции addTwo
num = num + 2; // на этапе выполнения контекста, эта строка изменит запись на {num: 7}
}
addTwo(num);
console.log(num); // 5
Но что же и этом произойдет со значением переменной num
из глобальной области видимости? В действительности её значение останется таким же, как и было до вызова функции addTwo
, так как в неё просто было скопировано значение этой переменной. И все манипуляции уже производились над другой переменной из области видимости функции addTwo
. В текущей ей реализации она просто производит операцию над локальной переменной и не возвращает никакого определенного значения (а точнее она вернёт undefined
).
Если переписать эту функцию, чтобы она возвращала новое значение локальной переменной num
и потом присвоить это значение обратно уже в глобальную переменную num
, то только в таком случае её значение поменяется:
let num = 5;
function addTwo(num) {
// на этапе создания контекста - {num: 5}
num = num + 2; // на этапе выполнения контекста, эта строка изменит запись на {num: 7}
return num; // вернуть результатом функции новое значение
}
num = addTwo(num); // присвоить результат функции как новое значение переменной
console.log(num); // 7
Этот пример отражает то, что примитивы передаются по значению и какие-либо действия производятся над копиями этих значений. Поэтому если явно не возвращать или не присваивать новый результат в соответствующие переменные, то никаких изменений переменных не произойдет.
Теперь рассмотрим как передаются значения объектов:
// Ссылочный тип object
let obj = { key: 5 };
// Функция для прибавления двойки к свойству объекта
function addTwoObj(obj) {
obj.key = obj.key + 2;
}
// Вызов функции
addTwoObj(obj);
console.log(obj.key); // 7
Почему в данном случае свойство объекта изменилось, хотя никаких дополнительных действий по возврату нового значения и его присваивания не происходило?
В этом случае важно понимать, что переменная содержит не сам объект, а грубо говоря указатель (ссылку) на то место в памяти, где этот объект хранится. Поэтому параметром в функцию будет передаваться именно это ссылка объект и Запись Окружения этой функции после вызова будет выглядеть так:
let obj = { key: 5 };
function addTwoObj(obj) {
// на этапе создания контекста - {obj: ссылка на объект { key: 5 } }, здесь переменная obj опять же является уже локальной переменной функции addTwoObj
obj.key = obj.key + 2; // на этапе выполнения контекста, эта строка сначала по переданной ссылке в obj найдет сам объект { key: 5 } изменит свойство key самого объекта, а не его копии. Запись Окружения станет {obj: ссылка на всё тот же объект, только уже с новым значением его свойства - { key: 7 } }
}
addTwoObj(obj); // переменная obj хранит в себе ссылку а объект { key: 5 }, поэтому параметром передаётся именно эта ссылка
console.log(obj.key); // Был изменен сам объект, который был передан по ссылке, поэтому значение его свойства будет 7
А что выведется в консоль, если изменить локальную переменную obj
?
let obj = { key: 5 };
function addTwoObj(obj) {
obj.key = obj.key + 2;
obj = { num: 6 }; // присвоить другой объект
obj = null; // или же вообще присвоить нулевое значение
}
addTwoObj(obj);
console.log(obj.key);
Здесь в консоли снова выведется 7
. Так как в функции addTwoObj
переменная obj
является локальной и затеняет одноименную глобальную переменную, то присваивания ей в функции новых значений никак не отразится на глобальной переменной obj
. Это лишь приведет к перезаписи переданной ссылки на объект { key: 5 }
новыми значениями.
let obj = { key: 5 };
function addTwoObj(obj) {
// на этапе создания контекста - {obj: ссылка на объект { key: 5 } }, здесь переменная obj опять же является уже локальной переменной функции addTwoObj
obj.key = obj.key + 2; // на этапе выполнения контекста, эта строка сначала по переданной ссылке в obj найдет сам объект { key: 5 } изменит свойство key самого объекта, а не его копии. Запись Окружения станет {obj: ссылка на всё тот же объект, только уже с новым значением его свойства - { key: 7 } }
obj = { num: 6 }; // после выполнения этой строки измениться лишь значение локальной переменной в Записи Окружения: {obj: ссылка на новый объект - {num: 6} }
obj = null; // а здесь вообще в локальную переменную запишется значение null примитивного типа {obj: null}
}
addTwoObj(obj); // переменная obj хранит в себе ссылку а объект { key: 5 }, поэтому параметром передаётся именно эта ссылка
console.log(obj.key); // Был изменен сам объект, который был передан по ссылке, поэтому значение его свойства будет 7
Обертки примитивных типов в JavaScript
В отличие от объектов, у примитивов нет своих методов, но у всех них, за исключением null
и undefined
, есть объектные аналоги, который оборачивает значение примитивного типа и позволяют производить над ними различные преобразования:
String
для string примитива.Number
для number примитива.Boolean
для boolean примитива.Symbol
для symbol примитива.
Что происходит когда вызывается какой-либо метод у примитивного типа данных, например:
let char = "текст".charAt(1); // е
Так как у примитивного типа строки нет своих методов, то сначала создаётся его копия и неявно оборачивается в его объектный аналог с помощью конструктора new String(something)
. И уже в рамках этого объекта существует набор различных встроенных методов, дин из них - charAt()
, который возвращает символ строки по указанной позиции. После вызова метода возвращается его результат и эта объектная обёртка уничтожается. Поэтому сам вызов метода никак не влияет на изначальное значение примитива, а только возвращает вычисленное значение.
Явно этот код можно записать так:
let char = new String("текст").charAt(1); // "е"
И именно поэтому вызов следующих методов будет лишь возвращать новые значения и никак не повлияет на исходную переменную str
:
let str = "текст";
let upper = str.toUpperCase();
let substr = str.substring(0, 3);
console.log(upper); // "ТЕКСТ"
console.log(substr); // "тек"
console.log(str); // "текст"
У каждой такой объектной обертки есть метод valueOf()
, который возвращает соответствующее значение примитивного типа. Например:
var numObj = new Number(10);
console.log(typeof numObj); // object
var num = numObj.valueOf();
console.log(num); // 10
console.log(typeof num); // number
Обычно не принято явно вызывать конструкторы для примитивных типов, так как они предназначены только для внутреннего использования и явное их использование без четкого понимания их поведения может приводить к различным ошибкам.
Оставить комментарий