Типы данных
В JS 8 типов данных, из них 7 примитивных, так как их значениями могут быть только простые значения (строка, или число, др.). 8й тип — это объект (хранят коллекции данных или более сложные структуры).
Строки полезны для хранения данных, которые можно представить в текстовой форме.
Синтаксис
let lang = "JS";
let lang = 'JS';
let langGreeting = `Привет,
учу
${a}`;
Одинарные кавычки = двойные кавычки. В обратные кавычки можно включать переменные и произвольные выражения, они могут занимать более одной строки.
Спецсимволы
Начинаются с \ — символа экранирования
\n (перевод строки), позволяет создавать многострочные строки
с помощью одинарных и двойных кавычек:
let c = 'Привет,\nJS'
\', \" (кавычки):
let person = 'I\'m,\nCococo'
\\ (обратный слеш)
\t (знак табуляции)
Операции со строками
Конкатенация строк (операция сложения).
При сложении двух строк получается новая строка, склеенная из исходных.
let like = 'Я люблю'
let lang = 'JS'
let result = like + ',' + lang // Я люблю, JS
Сравнение строк
Для сравнения используется лексикографический порядок (первые символы алфавита считаются меньше последних).
Алгоритм для произвольных строк s1 и s2:
- Сравниваются символы s1[0] и s2[0]. Если символы разные, то большей будет та строка, символ которой больше. Сравнение завершено.
- Если первые символы совпали, аналогично проверяем вторые символы. Продолжаем, пока не найдём несовпадение или не закончится одна из строк.
- Если строки закончились одновременно, то они равны. Если закончилась одна из строк, то большей строкой считается строка с большим количеством символов.
console.log('Код' > 'Кодер') // false
console.log('Кот' > 'Кодер') (true)
При этом: 'Кот' не равно 'кот', так как сравнение учитывает
регистр букв, если необходимо регистронезависимое сравнение,
то обе строки приводятся к верхнему или нижнему регистру
Важно понимать!
JavaScript различает объекты String и значения строкового примитива. Строковые литералы (обозначаемые кавычками) и строки, возвращённые вызовом String в неконструкторном контексте (то есть, без использования ключевого слова new) являются строковыми примитивами. Но если создать строку с помощью конструтора (new String), то это будет уже объектом.
let person1 = 'Anna';
let person2 = new String(person1);
console.log(typeof person1); // выведет 'string'
console.log(typeof person2); // выведет 'object'
Методы строк
Для того, чтобы на строковых примитивах использовать методы объекта String, JavaScript автоматически преобразует примитивы в объекты String, то есть создаётся специальный «объект-обёртка», который предоставляет нужную функциональность, а после удаляется. Подробнее о методах тут.
Числа — примитивный тип данных представляет как целочисленные значения, так и числа с плавающей точкой в диапазоне от -(253 — 1) до 253 — 1, а также специальные значения Infinity, -Infinity и NaN.
Синтаксис
let age = 100
let decimal = 0.101
let sameDecimal = .101
let billion = 1e9 // 1 миллиард, "e" производит
операцию умножения числа на 1 с указанным количеством нулей
или вот так с "e"
let ms = 1e-6 // 0.000001, отрицательное число
после "e" подразумевает деление на 1 с указанным количеством нулей
Запись в разных системах счисления
Шестнадцетиричная:
let number = 0xff // 255,
при выводе в консоль приводится к десятичной системе счисления
Двоичная:
let number = 0b11111111 // 255
Восьмеричная:
let number = 0o377 // 255
«Специальные числовые значения»:
- Infinity (математическая бесконечность),
- -Infinity (минус бесконечность),
- NaN (Not a number, вычислительная ошибка).
console.log( -1 / 0 ); // -Infinity
console.log( "Привет" / 2 ); // NaN, такое деление является ошибкой
// только одно исключение: NaN ** 0 равно 1
Методы
При использовании методов для чисел (как и для остальных примитивов) важно понимать механизм работы этих методов. Так, если к примитиву обратиться как к объекту (т.е. через точку), то в памяти ВРЕМЕННО для этого примитива создастся объектная версия (обертка) с помощью конструктора new Number (для других типов другие обертки), это и позволяет применять методы к примитивам.
Примеры:num.toString(base) возвращает строковое представление числа num в системе счисления base. base может варьироваться от 2 до 36 (по умолчанию 10).
Запись:
let number = 255
number.toString(16) // ff,
либо
255..toString(16)
num.toFixed([digits]) форматирует число, используя запись с фиксированной запятой. digits — количество цифр после десятичной запятой. Результатом метода будет являться строка. Данный метод позволяет решить в проблемы неточных вычислений в JS.
Запись:
let sum = 0.1 + 0.2;
console.log(sum); // 0.30000000000000004
дроби, такие как 0.1, 0.2, которые выглядят довольно просто
в десятичной системе счисления, на самом деле являются
бесконечной дробью в двоичной форме.
console.log(sum.toFixed(2)); // 0.30, помог метод toFixed()
Работа с числами
Математические операции
С числами можно выполнять стандартные математические операции (сложения +, вычитания -, умножения *, деления /, взятия остатка от целочисленного деления %), для определения приоритета операций пользуются скобками.
let a = 2
let b = 3
console.log(((a + b) * a % 5) ** 2) // 0
Операторы сравнения
— возвращают булевое значение
console.log(5 >= 10) // false
console.log(5 == 10) // false
console.log(5 != 10) // true
console.log(5 !== 10) // true
Math
В JavaScript встроен объект Math, который содержит различные математические функции и константы для работы с числами.
Округление
Math.floor — Округление в меньшую сторону
Math.ceil — Округление в большую сторону
Math.round — Округление до ближайшего целого
Math.trunc — Производит удаление дробной части без округления
let number = 255.222
Math.trunc(number) // 255
Другие математические функции
Math.random() — возвращает псевдослучайное число в диапазоне от 0 (включительно) до 1 (но не включая 1)
Math.random() // 0.019904487089602574 или иное
Math.max(a, b, c...) — возвращает наибольшее число из перечисленных аргументов.
Math.min(a, b, c...) — возвращает наименьшее число из перечисленных аргументов.
Math.min(1, 2) // 1
Math.pow(n, power) — возвращает число n, возведённое в степень power.
Math.pow(2, 10) // 2 в степени 10 = 1024
Другие полезные функции
- Преобразование к числу (+ или Number()).
- Преобразование к числу, если строка в точности не является числом: функции parseInt (возвращает целое число) и parseFloat (возвращает число с плавающей точкой).
- isNaN(value) — преобразует значение в число и проверяет, является ли оно NaN.
- isFinite(value) — преобразует аргумент в число и возвращает true, если оно является обычным числом, т.е. не NaN/Infinity/-Infinity.
- Number.isNaN(value) — возвращает true только в том случае, если аргумент принадлежит к типу number и является NaN. Во всех остальных случаях возвращает false.
- Number.isFinite(value) — возвращает true только в том случае, если аргумент принадлежит к типу number и не является NaN/Infinity/-Infinity. Во всех остальных случаях возвращает false.
let a = '2'
let b = '5'
console.log(+a + Number(b)) // 7
console.log(parseFloat('12.5em')) // 12.5
console.log(parseInt('100px')) // 100
console.log(parseInt('0xff', 16) ); // 255,
parseInt() имеет необязательный 2й параметр,
который определяет систему счисления.
Важно знать, что значение NaN уникально тем, что оно не является равным ничему другому, даже самому себе.
console.log(isNaN("str")) // true
console.log( NaN === NaN ) // false
console.log(isFinite("15")) // true
console.log(isFinite("str")) // false, потому что специальное значение: NaN
console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN("str" / 2)) // true
console.log(Number.isFinite(123)) // true
console.log(Number.isFinite(2 / 0)) // false
Cпециальный числовой примитив, который предоставляет возможность работать с целыми числами произвольной длины.
Синтаксис
Чтобы создать значение типа BigInt, необходимо добавить n в конец числового литерала или вызвать функцию BigInt, которая создаст число типа BigInt из переданного аргумента. Аргументом может быть число, строка и др.
let bigint = 1234567890123456789012345678901234567890n;
let sameBigint = BigInt("1234567890123456789012345678901234567890");
let bigNumber = BigInt(10) // то же самое, что и 10n
Работа с BigInt
Математические операторы
Для BigInt определены операции сложения +, вычитания -, умножения *, взятия остатка от деления %, возведение в степень **. Операция деления / также работает, но дробная часть будет отброшена. Унарный оператор '+' нельзя применить. Все операции с числами типа bigint возвращают bigint.
console.log(1n + 2n) // 3n
console.log(5n / 2n) // 2n
В математических операциях мы не можем смешивать bigint и обычные числа. Мы должны явно их конвертировать: используя либо BigInt(), либо Number().
let bigint = 1n;
let number = 2;
// конвертируем number в bigint
console.log(bigint + BigInt(number)); // 3n
// конвертируем bigint в number
console.log(Number(bigint) + number); // 3
Операции сравнения
Операции сравнения, такие как <, >, работают с bigint и обычными числами как обычно.
console.log(2n > 1n) // true
console.log(2n > 1) // true
Логические операции
В if или любом другом логическом операторе bigint число ведёт себя как обычное число.
if (0n) {
// никогда не выполнится, это false
}
Методы BigInt
Методы BigInt работают также, как и методы других примитивов. Если к BigInt обратиться как к объекту (т.е. через точку), то в памяти ВРЕМЕННО для этого примитива создастся объектная версия (обертка) что и позволит применить методы.
Примеры:
let bigNumber = 10n
console.log(bigNumber.toString()) // 10
console.log(bigNumber.valueOf()) // 10n
Логический или булев тип (boolean) может принимать лишь истинное (true) и ложное (false) значения. Назван он так в честь Джорджа Буля, одного из основателей математической логики.
Синтаксис
Явное указание значения
let truthyValue = true // «Истина»
let falsyValue = false // «Ложь»
Функция Boolean
let truthyValue = Boolean(1) // «Истина»
let falsyValue = Boolean('') // «Ложь»
Использование выражений, значениями которых будут «истина» или «ложь»
let truthyValue = Boolean(4 < 5)
let anotherTruthy = 4 < 5
console.log(truthyValue) // true
console.log(anotherTruthy) // true
Использование в условных выражениях
let isCorrect = true
if (isCorrect) {
// выполнится,
} else {
// не выполнится
}
Логическре преобразование типов
0, -0, null, false, NaN, undefined, пустая строка (""), становятся false. Все остальные значения — true.
let falsy1 = Boolean(),
falsy2 = Boolean(0),
falsy4 = Boolean(''),
falsy5 = Boolean(false)
let truthy1 = Boolean(true),
truthy3 = Boolean('false'),
truthy5 = Boolean([]),
truthy6 = Boolean({})
Методы Bollean
Методы Bollean работают также, как и методы других примитивов. Если к Bollean обратиться как к объекту (т.е. через точку), то в памяти ВРЕМЕННО для этого примитива создастся объектная версия (обертка) что и позволит применить методы.
Примеры:
let bool = true
console.log(bool.toString()) // true
console.log(bool.valueOf()) // true
Специальное значение null является примитивом. Оно формирует отдельный тип, который содержит только значение null, т.е. «ничего», «пусто» или «значение неизвестно».
Используется, когда нужно обозначить намеренное отсутствие значения. В контексте логических операций, рассматривается как ложное.
let age = null // значение age неизвестно
В языке существует похожий примитив undefined, он обозначает, что значение ещё не установлено. Их можно легко спутать, потому что оба обозначают отсутствие значения. Разница состоит в том, что null обозначает намеренное отсутствие, а undefined — неявное (неопределённость).
Важно знать!
Результатом вызова typeof null является «object». Это официально признанная ошибка в typeof, ведущая начало с времён создания JavaScript и сохранённая для совместимости. Конечно, null не является объектом. Это специальное значение с отдельным типом.
Важно знать!
При проверке на null или undefined, помните о различии между операторами равенства (==) и идентичности (===): с первым, выполняется преобразование типов.
null === undefined // false
null == undefined // true
У null нет соответствующего «объекта-обёртки», он не имеет никаких методов.
Примитивный тип данных, состоящий из одного значения undefined. Используется, чтобы обозначить неизвестное или неопределённое значение.
JavaScript автоматически устанавливает значение undefined объявленным переменным, которые не были проинициализированы значением (не присвоено значение).
Синтаксис
let age
console.log(age) // undefined
Функция возвращает undefined, если она не возвращает какого-либо значения.
function findValue() {
let a = 1
sum = a + 1
}
console.log(findValue()) // undefined
У undefined нет соответствующего «объекта-обёртки», он не имеют никаких методов.
Символ — примитивный тип, значения которого создаются с помощью вызова функции Symbol. Каждый созданный символ уникален. Символы могут использоваться в качестве имён свойств в объектах. Символьные свойства могут быть прочитаны только при прямом обращении и не видны при обычных операциях.
Синтаксис
«Символ» представляет собой уникальный идентификатор. Для создания символа нужно вызвать функцию Symbol. Создание символа через конструктор new Symbol() не поддерживается.
let id = Symbol()
При создании, символу можно дать описание (имя), в основном использующееся для отладки кода. Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание — это просто метка, которая ни на что не влияет.
let id = Symbol("name")
Использование
Символы используются для создания скрытых свойств объектов. В отличие от свойств, ключом которых является строка, символьные свойства может читать только владелец символа. Символьное свойство не появится в for...in, не будет модифицировано прямым обращением, так как другой скрипт не знает о нашем символе. Таким образом, свойство будет защищено от случайной перезаписи или использования.
Для использования символа при литеральном объявлении объекта {...}, его необходимо заключить в квадратные скобки.
let secondaryId = Symbol()
let user = {
'id': 193,
'name': 'Ольга',
[secondaryId]: 'olga-1'
}
for (const prop in user) {
console.log(prop, user[prop])
}
// id 193
// name Ольга
console.log(user[secondaryId]) // olga-1
Свойства
Symbol.length — содержит длину, всегда равную 0 (нулю).
Symbol.prototype — cодержит прототип конструктора Symbol.
Глобальные символы
Если созданный уникальный символ нужен в нескольких местах программы, то нужно воспользоваться глобальным реестром символов, который хранит символы по строковым ключам. При обращении по ключу всегда будет возвращаться один и тот же символ.
Для работы с этим реестром используется 2 метода:
- Symbol.for(ключ) — возвращает символ, хранящийся по ключу. Если символа ещё не существует, он создаётся автоматически
- Symbol.keyFor(символ) — возвращает строковый ключ, который хранит переданный символ или undefined, если символ не хранится в реестре.
let secondaryId = Symbol()
let user = {
'id': 193,
'name': 'Ольга',
[secondaryId]: 'olga-1'
}
console.log(Symbol.keyFor(secondaryId)) // undefined
let newSym = Symbol.for('registryKey')
user[newSym] = 'hello'
console.log(Symbol.keyFor(newSym)) // registryKey
Системные символы
Символы активно используются внутри самого JavaScript, чтобы определять поведение объектов. Самый известный символ Symbol.iterator, который позволяет реализовать обход конструкции с помощью синтаксических конструкций for...of.
Полезно знать
Чтобы обойти for...in и получить все свойства объекта с ключами-символами можно использовать встроенный метод Object.getOwnPropertySymbols(obj), который возвращает массив символов и позволяет получить символьные свойства конкретного объекта.
let object1 = {};
let a = Symbol('a');
let b = Symbol.for('b');
object1[a] = 'localSymbol';
object1[b] = 'globalSymbol';
const objectSymbols = Object.getOwnPropertySymbols(object1);
console.log(objectSymbols) // [Symbol(a), Symbol(b)]
Объекты — непримитивный типа данных, используются для хранения коллекций различных значений и более сложных сущностей.
Синтаксис
Объект может быть создан с помощью:
- литерала объекта — фигурных скобок {…} с необязательным списком свойств.
Свойство — это пара «ключ: значение», где ключ/имя/идентификатор – это строка/символ (ограничений к именам нет — можно назвать let и др.), а значение может быть чем угодно (в т.ч. другой объект, массив, функция (об этом далее)).
- конструктора объекта new Object()
const user = new Object({title: 'Война и мир', author: 'Лев Толстой'})
const user = {
name: "John",
age: 30,
children: ['Rita', 'Alex'],
favoriteBook: {
title: '1984',
author: 'Orwell',
},
"likes cats": true,
};
// имя свойства может состоять из нескольких слов,
// но тогда оно должно быть заключено в кавычки (любой их тип)
// последнее свойство может заканчиваться "висячей запятой"
Важно знать и понимать!
Одно из фундаментальных отличий объектов от примитивов заключается в том, что объекты хранятся и копируются «по ссылке» ("адресу в памяти"), тогда как примитивные значения: строки, числа, логические значения и т.д. — всегда копируются «как целое значение». Поэтому даже если переменная, которой присвоен объект (хранит ссылку на этот объект), объявлена через const, свойства хранимого объекта можно менять. Изменение внутреннего состояния не изменяет ссылку.
Свойства объекта
Для обращения к свойствам используется запись «через точку». Для свойств, имена которых состоят из нескольких слов для доступа к свойствам используются квадратные скобки (внутри них кавычки любого типа). В большинстве случаев, когда имена свойств известны и просты, используется запись через точку (можно и [], если хочется).
console.log(user.name) // John
console.log(user.age) // 30
console.log(user["likes cats"]) // true
Для добавления свойства обращаемся к нему и используем оператор присваивания "=".
user.isDev = true
console.log(user.isDev) // true
user["likes dogs"] = true
console.log(user["likes dogs"]) // true
Для удаления свойства используется оператор delete. Но чаще свойства не удаляют, а сбрасывают значение, устанавливая undefined.
delete user.age
delete user["likes cats"];
user["likes cats"] = undefined
Для проверки существования свойства в объекте используется специальный оператор "in".
let user = {name: "John", age: 30};
console.log("age" in user) // true, user.age существует
console.log("blabla" in user) // false, user.blabla не существует
Для перебора всех свойств объекта используется цикл for...in. При таком переборе свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания.
let user = {
name: "John",
age: 30,
};
user.isDev = true
for (let key in user) {
console.log(key) // name, age, isDev - значения ключей
console.log(user[key]) // John, 30, true
}
Методы объекта
Свойства объекта могут быть функциями. Метод — это функция в объекте, которая дает возможность "что-то делать" нашему объекту.
Метод можно создать через присвоение свойству функции, созданной через Function Expression / Function Declaration.
// пример 1
let user = {name: "John"};
user.sayHi = function() {
console.log("Привет!");
}; // присвоили свойству функции созданной через Function Expression.
user.sayHi() // Привет!
// пример 2
let cat = {
name: 'Кузя',
play: function() {
console.log('Люблю играть')
}, // полная запись
speak() {
console.log('Мяу')
} // сокращенная запись
}
cat.play() // Люблю играть
cat.speak() // Мяу
Методу объекта обычно требуется доступ к информации, хранящейся в объекте. Для доступа к ней внутри объекта метод может использовать ключевое слово this. Подробнее о нем тут.
Сравнение объектов
JavaScript сравнивает не значения свойств объектов, а адреса в памяти, по которым эти объекты хранятся. Поэтому любое сравнение двух объектов будет возвращать false, даже если они выглядят одинаково.
const book = {title: 'Дюна'} // один объект
const anotherBook = {title: 'Дюна'} // другой объект
console.log(book === anotherBook) // false
Сравнение будет возвращать true, только если мы сравниваем переменные, указывающие на один и тот же объект.
const book = {title: 'Дюна'}
const anotherBook = book // в anotherBook записывается ссылка на тот же объект
console.log(book === anotherBook) // true
Копирование объекта
При копировании переменной копируется ссылка, но сам объект не дублируется.
let user = {name: 'John'};
let admin = user;
admin.name = 'Pete'; // изменено по ссылке из переменной "admin"
console.log(user.name); // 'Pete', изменения видны по ссылке из переменной "user"
Клонирование объекта
Перебор свойств и «копирование на примитивном уровне»
let user = {
name: "John",
age: 30
};
let clone = {}; // новый пустой объект
for (let key in user) {
clone[key] = user[key];
} // копируем все свойства user в него
clone.name = "Pete"; // изменим в нём данные
console.log(user.name); // все ещё John в первоначальном объекте
Метод Object.assign
Метод копирует свойства всех исходных объектов в целевой объект и возвращает целевой объект («поверхностную копию», так как вложенные объекты копируются по ссылке).
Синтаксис
Object.assign(dest, [src1, src2, src3...])
// dest — целевой объект.
// аргументы src1, ..., srcN - исходные объекты
Примеры
let user = {name: "John"};
let permissions1 = {canView: true};
let permissions2 = {canEdit: true};
Object.assign(user, permissions1, permissions2);
// теперь user = {name: "John", canView: true, canEdit: true}
// можно использовать для замены цикла for..in для простого клонирования
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
Объект — прародитель
В JavaScript объект является прародителем всех других сущностей. Все типы данных и структуры, кроме примитивных, являются потомками объекта. Так как объект — это сущность с набором свойств то, т.о., массив и функция — это тоже объекты.
const shows = ['Breakind Bad', 'The Office', 'Silicon Valley']
shows.length // это свойство массива
Про функцию еще можно сказать, что это объект первого класса. Это означает, что функцию можно использовать так же, как и другие типы данных: сохранять в переменную, передавать аргументом и возвращать из функции.
function sum(a, b) {
return a + b
}
sum.arguments // можно вызвать свойство функции
sum.someField = 'value' // можно присвоить значение в поле
console.log(sum.name) // sum
console.log(sum.length) // 2
console.dir(sum) // способ посмотреть свойства заданного объекта
ответ консоли
ƒ sum(a, b)
someField: "value"
arguments: null
caller: null
length: 2
name: "sum"
prototype: {constructor: ƒ}
[[Prototype]]: ƒ ()
В выводе консоли есть и свойство someField, которое мы присвоили, и набор встроенных свойств и методов.
Если развернуть [[Prototype]]: ƒ (), то прототипом текущего прототипа как раз является объект, поэтому его и можно назвать прародителем (о прототипах подробнее тут).
Объект также является прототипом прототипов Map, Set, WeakMap, WeakSet.
Кроме того, в JS есть еще встроенные объекты, такие как Date и Math.
Объекты-обёртки
Каждый примитив имеет свой собственный «объект-обёртку» (кроме null и undefined). Конструкторы для примитива являются наследниками объекта и предназначены только для внутреннего пользования. Создание «объектов-обёрток» для примитивов при помощи такого синтаксиса как, например, new Number(1) или new Boolean(false) в JavaScript, очень не рекомендуется.
console.log(typeof 0) // "число"
console.log(typeof new Number(0)) // "object"!
Проверка типа
Здесь можно проверить тип данных. Введи примитивный тип данных, либо объект, созданный с помощью литерала, либо символ, созданный с помощью Symbol().