Значения и переменные: числа, строки, типы
Зачем это нужно
Предыдущая глава объяснила, чем TypeScript отличается от JavaScript, но обошла стороной вопрос, с которого на самом деле начинается программирование: что вообще лежит в переменной и по каким правилам оно себя ведёт.
Это не мелочь и не разминка перед «настоящими темами». Почти все ошибки, которые студент ловит на первом чемпионате, живут именно здесь: комиссия посчиталась как NaN, потому что где-то к числу прибавили строку; сумма перевода получилась 0.30000000000000004; сравнение адресов через == сработало не так, как ожидалось; поле height оказалось undefined, и программа упала через три функции после места ошибки.
Поэтому здесь мы разбираем всё максимально медленно: что такое значение, что такое переменная, чем let отличается от const, какие типы вообще существуют в языке, почему число в JavaScript одно на всё, и что происходит, когда типы не совпадают. Каждый вывод в этой главе снят с живого запуска Node.js 24 и компилятора TypeScript 6 — ни одного «примерно так должно быть».
- Зачем это нужно
- Значение и переменная — это два разных предмета
- Объявление:
const,letи почемуvarне пишут - Область видимости и почему обращение «раньше времени» падает
- Какие типы вообще бывают: живой вывод
typeof - Число: одно на всё, и что из этого следует
- Строка: кавычки, шаблоны, длина, неизменяемость
- Логическое значение, truthy и falsy
nullиundefined: две разные пустоты- Приведение типов: что происходит, когда типы не совпали
- Сравнение:
===против== - Аннотации типов: где их писать и где не нужно
- Литеральные типы, union и
as const - Имена переменных: правила языка и правила людей
- Типичные ошибки: текст, перевод, причина, починка
- Тренажёр: строки из этой главы
- Символы этой главы
- Что должен уметь
- Куда дальше: проверенные ресурсы
Значение и переменная — это два разных предмета
Первое, что нужно развести в голове, — значение и переменная.
Значение — это данные. Число 5. Строка 'Олег'. Логическое true. Значение само по себе ни в чём не хранится, оно просто есть: когда ты пишешь в коде 5, это не «переменная со значением пять», это само число пять, записанное прямо в тексте программы. Такая запись называется литерал — значение, записанное буквально.
Переменная — это имя, привязанное к значению. Не коробка, в которую значение положили, а именно ярлык, который на значение навесили. Разница проявляется сразу, как только одно и то же значение получает два имени.
const a = 5;
const b = a; // b — второй ярлык, но не на «то же место», а на то же значение
Правильная картинка для первых недель: слева имя, справа значение, между ними знак =, который читается не «равно», а «пусть отныне это имя указывает вот на это». Знак = в программировании — не утверждение о равенстве, а команда. Именно поэтому строка x = x + 1 не является абсурдом: справа вычисляется старое значение плюс один, слева имя x переприкрепляется к результату.
Читай = вслух как «получает» или «становится». const fee = 0.001 — «fee получает ноль целых одну тысячную». Тогда x = x + 1 перестаёт выглядеть как математическая ошибка и начинает читаться как «x становится тем, чем был, плюс один». Для сравнения на равенство в языке есть отдельный знак — ===, о нём ниже.
Объявление: const, let и почему var не пишут
Чтобы имя появилось, его нужно объявить. В современном JavaScript и TypeScript для этого есть два слова.
const fee = 0.001; // имя, которое больше не переприкрепить
let height = 152340; // имя, которое можно переприкрепить
Разбор первой строки по кускам:
const не означает «неизменяемое значение». Он означает «имя нельзя переприкрепить». Если под именем лежит объект или массив, содержимое менять можно — запрещено только заменить сам объект другим. Это одно из самых частых недоразумений первого месяца, и мы вернёмся к нему в главе про коллекции.
const tx = { amount: 5 };
tx.amount = 7; // можно: объект тот же, поменяли поле
// tx = { amount: 7 }; // нельзя: попытка перевесить ярлык
Вот что говорит Node на вторую попытку — текст снят с живого запуска:
TypeError: Assignment to constant variable.
Перевод: «попытка присвоить константной переменной». Причина — переприкрепление имени, объявленного через const. Починка — либо не переприкреплять, либо объявить через let, если переприкрепление действительно нужно.
Правило выбора простое: по умолчанию всегда const. Пишешь let только тогда, когда компилятор или логика прямо требуют переприсваивания — например, счётчик в цикле или переменная, которая заполняется в одной из веток условия. Это не догма ради красоты: const сообщает читателю кода, что значение дальше по функции не изменится, и снимает половину вопросов при чтении.
Третье слово, var, существует, но в новом коде его не пишут. Оно старше остальных и ведёт себя иначе: игнорирует блоки (переменная, объявленная внутри if, видна за его пределами) и допускает обращение к себе до объявления, молча выдавая undefined. Знать про var нужно ровно затем, чтобы понимать чужой код десятилетней давности и не удивляться, встретив его в старом контракте.
Область видимости и почему обращение «раньше времени» падает
Область видимости — участок кода, где имя существует. Для const и let это блок: пара фигурных скобок, внутри которых имя объявлено.
if (true) {
const inside = 'вижу только здесь';
console.log(inside); // работает
}
// console.log(inside); // ReferenceError: inside is not defined
Реальный текст ошибки при обращении к несуществующему имени:
ReferenceError: reciever is not defined
Перевод: «имя reciever не определено». В девяти случаях из десяти это опечатка — здесь, например, в слове receiver пропущена буква. Node не догадывается, что ты имел в виду, он просто не находит такого имени.
Есть и вторая, более коварная ошибка — обращение к имени до строки, где оно объявлено:
console.log(amount);
let amount = 5;
ReferenceError: Cannot access 'amount' before initialization
Перевод: «нельзя обратиться к amount до инициализации». Имя уже известно движку (он прочитал весь блок целиком до начала выполнения), но пользоваться им нельзя до строки объявления. Промежуток между началом блока и строкой let amount = 5 называют временной мёртвой зоной, в англоязычной документации — temporal dead zone, TDZ. Обрати внимание на важную деталь: сообщение здесь другое, чем в случае опечатки. is not defined — имени нет вообще; Cannot access ... before initialization — имя есть, но ты пришёл слишком рано. По тексту ошибки эти два случая различаются мгновенно.
Учись читать не только слово ошибки, но и её вид целиком. Node печатает строку исходника и ставит ^ ровно под местом проблемы, затем тип ошибки (TypeError, ReferenceError, SyntaxError), затем текст, затем стек вызовов. Тип ошибки уже сужает поиск: ReferenceError — проблема с именем, TypeError — имя нашлось, но значение под ним не то, SyntaxError — движок вообще не смог разобрать текст, и до выполнения дело не дошло.
Какие типы вообще бывают: живой вывод typeof
В JavaScript у каждого значения есть тип. Узнать его можно оператором typeof. Вот результат живого запуска — не пересказ таблицы из документации, а то, что напечатал Node:
console.log(typeof 152340); // number
console.log(typeof 'привет'); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object ← да, именно так
console.log(typeof [1, 2]); // object
console.log(typeof { a: 1 }); // object
console.log(typeof console.log); // function
console.log(typeof 10n); // bigint
Семь типов, из которых состоит всё остальное:
| Тип | Что это | Пример литерала |
|---|---|---|
number | число — целое и дробное одновременно | 42, 0.001, -7 |
string | строка, текст | 'Олег', "3PJa", `высота ${h}` |
boolean | логическое: да или нет | true, false |
undefined | «значения не положили» | undefined |
null | «значение осознанно пустое» | null |
bigint | целое число любой длины | 10n |
symbol | уникальный ключ, в учебном коде почти не встречается | Symbol('id') |
Всё, что не из этого списка, — объект: массивы, обычные объекты, даты, функции. Отсюда и typeof [1,2] === 'object'.
Строка typeof null даёт 'object', и это не логика, а ошибка первых версий языка, которую невозможно исправить: слишком много кода в мире на неё опирается. Проверять на null через typeof бессмысленно, для этого пишут прямое сравнение value === null.
Массив тоже объект, поэтому typeof для него бесполезен. Чтобы отличить массив от обычного объекта, есть отдельная встроенная проверка Array.isArray(value) — она возвращает true только для массивов. Пригодится, когда нода вернёт то массив транзакций, то объект с ошибкой.
Число: одно на всё, и что из этого следует
В JavaScript нет отдельных целых и дробных типов. Нет int, нет long, нет float и double — есть один number, который внутри всегда представлен как число с плавающей точкой двойной точности по стандарту IEEE 754. И 1, и 0.5, и 152340 — один и тот же тип.
Это удобно ровно до того момента, пока не начинаешь считать деньги. Живой вывод:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log((0.1 + 0.2).toFixed(2)); // '0.30' ← строка, не число!
Причина не в JavaScript, а в двоичном представлении: 0.1 и 0.2 в двоичной системе — бесконечные дроби, ровно как 1/3 в десятичной. Их приходится округлить при хранении, и погрешность вылезает при сложении. Точно так же ведут себя double в Java, Kotlin, C++ и Python.
Практический вывод для блокчейна: суммы никогда не хранят в дробных числах. В Waves, как и в большинстве сетей, сумма хранится в минимальных неделимых единицах, целым числом. Одна монета — это 10⁸ таких единиц, и перевод «5 монет» уезжает в сеть как 500000000. Никаких 5.0 в теле транзакции нет, и это сделано именно ради того, чтобы 0.1 + 0.2 никогда не превратилось в 0.30000000000000004 на балансе.
Ещё три вывода живого запуска, которые надо знать наизусть:
console.log(9007199254740991 + 2); // 9007199254740992 ← прибавили 2, получили +1
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
Number.MAX_SAFE_INTEGER равен 9007199254740991 — это последнее целое, для которого арифметика гарантированно точна. Выше начинается зона, где числа «прыгают»: прибавление двойки дало прирост на единицу. Это ровно та зона, куда попадают суммы в минимальных единицах у крупных балансов, поэтому для них существует отдельный тип.
bigint — целое произвольной длины. Записывается с суффиксом n:
console.log(10n * 3n); // 30n
console.log(typeof 10n); // bigint
Смешивать bigint и number в одном выражении язык запрещает — придётся привести явно. Зато точность не теряется никогда, сколько бы цифр ни было.
NaN — «not a number», результат бессмысленной арифметики. У него две особенности, обе из живого вывода:
console.log(NaN === NaN); // false ← NaN не равен даже самому себе
console.log(Number.isNaN(NaN)); // true ← правильная проверка
console.log(typeof NaN); // number ← «не число» имеет тип «число»
Проверять на NaN сравнением бесполезно, для этого есть Number.isNaN.
toFixed(2) возвращает строку, а не число. Это причина классической цепочки багов: посчитали сумму, красиво округлили через toFixed, а дальше сложили с другим числом — и вместо арифметики получили склейку строк. Если после округления нужно продолжать считать, число возвращают обратно: Number(value.toFixed(2)). А лучше — не округляют вообще, а считают в целых минимальных единицах и форматируют только на самом выходе, перед показом человеку.
Строка: кавычки, шаблоны, длина, неизменяемость
Строка — последовательность символов. Записывается тремя способами:
const single = 'одинарные кавычки';
const double = "двойные кавычки";
const back = `обратные кавычки`;
Первые два вида полностью равнозначны — выбор чисто стилистический, в большинстве проектов договариваются об одинарных. Третий вид принципиально другой: внутри обратных кавычек работает подстановка значений.
const name = 'Олег';
console.log(`Привет, ${name}! Длина: ${name.length}`);
// Привет, Олег! Длина: 4
Конструкция ${...} называется интерполяцией: внутрь фигурных скобок можно поставить любое выражение, оно вычислится и превратится в строку. Такая строка называется шаблонной. Второе её свойство — она может занимать несколько строк без всяких спецсимволов.
Разбор шаблонной строки по частям:
Живой вывод основных операций над строкой:
console.log('Привет'.length); // 6 — количество символов
console.log('Привет'.toUpperCase()); // ПРИВЕТ
console.log('Привет'[0]); // П — обращение по индексу, счёт с нуля
console.log('Привет'.at(-1)); // т — отрицательный индекс считает с конца
Индексы начинаются с нуля. Первый символ — нулевой, последний — под номером length - 1. Это не прихоть JavaScript, так устроено почти во всех языках, и это источник самой популярной ошибки в программировании — «промах на единицу».
Строки неизменяемы. Ни один метод строки не меняет её, все возвращают новую:
const s = 'привет';
s.toUpperCase(); // вернулось 'ПРИВЕТ', но…
console.log(s); // привет — исходная строка не изменилась
const loud = s.toUpperCase(); // так правильно: сохранили результат
Ошибка «вызвал метод и забыл сохранить результат» встречается у всех и не даёт никакой ошибки при выполнении: код просто ничего не делает.
Логическое значение, truthy и falsy
Тип boolean состоит ровно из двух значений: true и false. Но в условиях язык принимает не только их — он умеет превращать в логическое любое значение. Значения, которые превращаются в false, называют falsy, остальные — truthy.
Falsy-значений ровно восемь, и их проще выучить наизусть, чем каждый раз проверять:
false 0 -0 0n '' null undefined NaN
Всё остальное truthy. В том числе то, что интуиция подсказывает считать пустым:
if ([]) { /* сюда зайдёт: пустой массив truthy */ }
if ({}) { /* сюда зайдёт: пустой объект truthy */ }
if ('0') { /* сюда зайдёт: непустая строка truthy, даже если в ней ноль */ }
Отсюда растёт очень частый баг: проверка if (height) пропустит настоящую высоту 0, потому что ноль falsy. Правильная проверка на «значение не пришло» — сравнение с undefined или null, а не проверка на истинность.
Из того же корня растёт разница между двумя похожими операторами:
console.log(0 || 'запасное'); // 'запасное' — || смотрит на truthy, ноль его не устроил
console.log(0 ?? 'запасное'); // 0 — ?? смотрит только на null и undefined
|| возвращает правую часть, если левая falsy. ?? (оператор нулевого слияния, nullish coalescing) возвращает правую часть, только если левая — null или undefined. Для значений по умолчанию почти всегда нужен ??: он не съедает валидные ноль и пустую строку.
null и undefined: две разные пустоты
В большинстве языков пустота одна. В JavaScript их две, и разница смысловая.
undefined — «значение сюда не клали». Оно появляется само: у объявленной без присваивания переменной, у отсутствующего поля объекта, у функции без return.
null — «сюда осознанно положили пустоту». Само по себе оно не появляется никогда, его пишет человек или присылает внешняя система.
Живой вывод по сравнениям:
console.log(null == undefined); // true — нестрогое сравнение считает их равными
console.log(null === undefined); // false — строгое различает
Именно поэтому у проверки на «пусто» есть удобная идиома: value == null — единственный случай, когда двойное равенство пишут осознанно. Одной проверкой ловятся обе пустоты сразу.
В TypeScript обе пустоты становятся видимыми, когда включён strict. Живая ошибка компилятора:
let maybe: string | undefined;
maybe.toUpperCase();
error TS18048: 'maybe' is possibly 'undefined'.
Перевод: «maybe, возможно, undefined». Компилятор не запрещает работать с таким значением — он требует сначала проверить:
if (maybe !== undefined) {
maybe.toUpperCase(); // здесь компилятор уже спокоен
}
Без проверки в чистом JavaScript та же ситуация даёт ошибку уже во время выполнения, и её текст надо уметь узнавать:
TypeError: Cannot read properties of undefined (reading 'address')
Перевод: «нельзя прочитать свойства undefined (читаем address)». Разбор: где-то слева от .address оказалось undefined. В строке tx.sender.address виновник не tx и не address, а именно tx.sender — поля sender в объекте не было. Это самая частая ошибка при работе с ответом ноды: приходит объект без ожидаемого поля.
Приведение типов: что происходит, когда типы не совпали
JavaScript почти никогда не отказывается выполнять операцию. Вместо этого он приводит значения к тому типу, который считает подходящим. Живой вывод — вот здесь стоит остановиться и разобрать каждую строку:
console.log('5' * 2); // 10 — умножения строк не бывает, строка стала числом
console.log('5' + 2); // '52' — плюс у строк означает склейку, число стало строкой
console.log(5 + '2'); // '52' — порядок не важен, строка «побеждает»
console.log(true + 1); // 2 — true превращается в 1
console.log(null + 1); // 1 — null превращается в 0
console.log(undefined + 1); // NaN — undefined в число не превращается
console.log([] + {}); // '[object Object]' — и это строка
Главный вывод: + — единственный оператор с двойным смыслом. Если хоть одна сторона строка, + склеивает. Все остальные арифметические операторы (-, *, /, %) значения приводят к числу.
Отсюда правило, которое экономит часы: данные, пришедшие извне — из формы, из аргументов командной строки, из JSON, — приводи к числу явно и сразу, на границе, а не в глубине расчёта.
console.log(Number('12abc')); // NaN — строгий разбор: либо всё число, либо NaN
console.log(Number('')); // 0 — пустая строка становится нулём
console.log(Number(' 7 ')); // 7 — пробелы по краям игнорируются
console.log(parseInt('12abc')); // 12 — читает цифры с начала и останавливается
console.log(parseFloat('3.5кг'));// 3.5
Number() и parseInt() ведут себя по-разному, и это надо помнить: Number требует, чтобы вся строка была числом, parseInt откусывает начало. Для проверки данных нужен Number: он честно вернёт NaN там, где parseInt сделает вид, что всё в порядке.
Проверка «пришло ли настоящее число» пишется в две части и почти всегда именно так: сначала преобразование, потом Number.isFinite. const n = Number(raw); if (!Number.isFinite(n)) throw new Error('не число');. isFinite отсекает сразу три беды: NaN, Infinity и -Infinity. Пропускать этот шаг на границе с внешним миром — гарантированно получить NaN в теле транзакции.
Сравнение: === против ==
Операторов сравнения на равенство два, и разница между ними в одном: приводит ли он типы перед сравнением.
console.log(1 === '1'); // false — строгое: разные типы, дальше не смотрим
console.log(1 == '1'); // true — нестрогое: строка приводится к числу, потом сравнивается
Правило: всегда === и !==. Единственное общепринятое исключение — проверка value == null, ловящая сразу null и undefined.
Причина строгости не в педантизме. Правила приведения при == длинные, несимметричные и запоминанию не поддаются: '' == 0 истинно, '0' == 0 истинно, но '' == '0' ложно. Проще не иметь с этим дела вовсе.
Сравнения >, <, >=, <= работают и с числами, и со строками. Для строк сравнение идёт по кодам символов, слева направо — то есть 'a' < 'b' истинно, а 'Я' < 'а' тоже истинно, потому что заглавная кириллица идёт в таблице раньше строчной. Сортировать так фамилии нельзя, для этого есть отдельные средства сравнения с учётом языка.
Аннотации типов: где их писать и где не нужно
Всё, что было выше, — общее для JavaScript и TypeScript. Теперь часть, которой в JavaScript нет.
Аннотация типа — двоеточие и имя типа после имени переменной или параметра:
const fee: number = 0.001;
let recipient: string = '3PJaBz4';
function feeFor(txSize: number): number { return txSize * fee; }
Но в первых двух строках аннотация лишняя. TypeScript умеет вывод типа: он смотрит на значение справа и сам понимает, что там число или строка. Записанное вручную повторяет очевидное и только загромождает код.
Правило, принятое почти везде:
- Не пишем тип, когда переменная сразу получает значение:
const fee = 0.001уже число. - Пишем тип у параметров функции: вывести его неоткуда, компилятор без аннотации ничего не знает.
- Пишем тип у переменной, объявленной пустой:
let result: string | undefined;. - Пишем тип результата у функций, которые видны из других файлов, — как договор для читателя.
- Пишем тип на границе с внешним миром: всё, что приехало из сети, файла или аргументов.
Что происходит, если аннотацию нарушить, — живые сообщения компилятора:
let height: number = 152340;
height = 'сто';
error TS2322: Type 'string' is not assignable to type 'number'.
Перевод: «тип string нельзя присвоить типу number». Номер TS2322 — самая частая ошибка в TypeScript; увидев её, ищи место, где значение одного типа кладут в имя другого.
function feeFor(txSize: number): number { return txSize * 0.001; }
feeFor('100');
feeFor();
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
error TS2554: Expected 1 arguments, but got 0.
Перевод: «аргумент типа string не подходит параметру типа number» и «ожидался 1 аргумент, получено 0». Обе ошибки — про вызов, а не про саму функцию. Обрати внимание, что вторую ошибку JavaScript не заметил бы вовсе: он спокойно вызвал бы функцию, положил в txSize значение undefined и вернул NaN.
Проверь глазами разницу двух номеров: TS2322 — присваивание («значение не подходит переменной»), TS2345 — аргумент («значение не подходит параметру»). Номера ошибок TypeScript устойчивы между версиями, и поиск в интернете по номеру находит объяснение быстрее, чем поиск по тексту.
Литеральные типы, union и as const
Тип в TypeScript не обязан быть одним из встроенных. Типом может быть конкретное значение:
let status: 'ok' = 'ok';
Само по себе это бесполезно, но в связке с объединением через | получается самая полезная конструкция первых недель:
type TxStatus = 'unknown' | 'unconfirmed' | 'confirmed';
let s: TxStatus = 'confirmed'; // можно
// let bad: TxStatus = 'maybe'; // нельзя
Живая ошибка на последней строке:
error TS2322: Type '"maybe"' is not assignable to type '"fail" | "ok"'.
Компилятор перечисляет допустимые значения прямо в тексте ошибки — это удобнее любой документации. Такой набор строк заменяет то, что в других языках называется перечислением, и в отличие от него ничего не добавляет в собранный JavaScript: после компиляции остаётся обычная строка.
Слово type вводит псевдоним типа — имя для описания, которое дальше используют вместо длинной записи. Псевдоним не создаёт нового типа в рантайме, он существует только для компилятора.
Ещё одна конструкция, которую стоит знать сразу, — as const:
const config = { url: 'http://localhost:6862', retries: 3 } as const;
Без as const компилятор выведет url: string. С ним — url: 'http://localhost:6862', точное значение, и все поля станут только для чтения. Это то, что нужно для настроек: случайное изменение поля перестанет компилироваться.
Имена переменных: правила языка и правила людей
Правила языка короткие. Имя может состоять из букв, цифр, знаков _ и $, но не может начинаться с цифры. Регистр важен: height и Height — разные имена. Зарезервированные слова (const, if, class, return и ещё пара десятков) именами быть не могут.
Правила людей длиннее и важнее:
- camelCase для переменных и функций:
txSize,nodeUrl,makeTransfer. Первое слово со строчной, каждое следующее с заглавной. - PascalCase для типов, интерфейсов и классов:
TxStatus,NodeStatus,ContractClient. - UPPER_SNAKE_CASE для настоящих констант-настроек:
MAX_RETRIES,NODE_URL. - Латиница, английские слова. Технически имя
высотаработает, но в общем коде так не пишут: библиотеки, ошибки и документация всё равно на английском, и смесь читается хуже. - Имя говорит, что внутри, а не какого оно типа.
recipientAddressлучше, чемstr2. - Короткие имена допустимы там, где жизнь переменной — две строки:
iв цикле,tв маленькой функции. В длинной функции такое имя превращается в загадку.
Отдельная ловушка TypeScript в проекте, где подключена библиотека типов браузера: некоторые простые слова уже заняты глобальными именами. Живой пример — попытка объявить let name = 'Олег' на верхнем уровне файла даёт error TS2451: Cannot redeclare block-scoped variable 'name', потому что name уже объявлен в описаниях DOM. То же с status, length, top. Лечится либо более точным именем (userName, txStatus — что и так лучше), либо тем, что код лежит внутри функции или модуля, а не на верхнем уровне скрипта.
Типичные ошибки: текст, перевод, причина, починка
Собранная таблица всех сообщений, которые встретились в этой главе, — каждое снято с живого запуска.
| Сообщение | Перевод | Что произошло | Как чинить |
|---|---|---|---|
TypeError: Assignment to constant variable. | Присваивание константной переменной | Попытка переприкрепить имя, объявленное через const | Не переприсваивать; если нужно — объявить через let |
ReferenceError: reciever is not defined | Имя не определено | Опечатка в имени или объявление в другом блоке | Проверить написание, проверить область видимости |
ReferenceError: Cannot access 'amount' before initialization | Обращение до инициализации | Использование let/const выше строки объявления | Перенести объявление выше места использования |
TypeError: Cannot read properties of undefined (reading 'address') | Нельзя прочитать свойства undefined | Слева от точки оказалось undefined — поля не было в объекте | Проверить наличие поля: tx.sender?.address или явная проверка |
TypeError: h.toFixed is not a function | h.toFixed не функция | Метод числа вызвали у строки | Привести к числу: Number(h).toFixed(2) |
error TS2322: Type 'string' is not assignable to type 'number'. | Строку нельзя присвоить числу | Значение одного типа кладут в имя другого | Исправить значение или пересмотреть аннотацию |
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. | Аргумент не подходит параметру | Вызов функции с неверным типом аргумента | Привести аргумент к нужному типу на границе |
error TS2554: Expected 1 arguments, but got 0. | Ожидался 1 аргумент, получено 0 | Забыли передать аргумент | Передать аргумент или сделать параметр необязательным |
error TS18048: 'maybe' is possibly 'undefined'. | Возможно undefined | Использование значения, которое может отсутствовать | Проверить перед использованием |
error TS2551: Property 'toFixed' does not exist on type '"привет"'. Did you mean 'fixed'? | У строки нет toFixed | Метод одного типа вызвали у другого | Использовать метод, существующий у этого типа |
error TS2339: Property 'whatever' does not exist on type 'number'. | У числа нет такого свойства | Обращение к несуществующему свойству | Проверить, что действительно лежит в переменной |
Тренажёр: строки из этой главы
Цель: скорость ≥ 110 зн/мин, точность ≥ 90%
const fee = 0.001;
Символы этой главы
| Знак | Название | Что делает |
|---|---|---|
= | присваивание | привязывает имя к значению справа |
=== / !== | строгое сравнение | сравнивает без приведения типов; рабочий вариант по умолчанию |
== / != | нестрогое сравнение | приводит типы перед сравнением; осознанно пишут только как == null |
: | аннотация типа | «это имя имеет такой тип»; только TypeScript |
| ` | ` | объединение типов |
` | обратная кавычка | шаблонная строка с подстановкой |
${ } | интерполяция | подставляет значение выражения в шаблонную строку |
?? | нулевое слияние | запасное значение только для null и undefined |
два знака | подряд | логическое ИЛИ | запасное значение для любого falsy |
?. | опциональная цепочка | обращение к полю, которое может отсутствовать, без падения |
n | суффикс bigint | 10n — целое произвольной длины |
; | точка с запятой | конец инструкции |
// | комментарий | всё до конца строки игнорируется |
Что должен уметь
- Объяснять разницу между значением и переменной и читать
=как «получает», а не «равно». - Выбирать между
constиletосознанно и объяснять, почемуconstне запрещает менять поля объекта. - Отличать по тексту ошибки отсутствующее имя от обращения до инициализации.
- Перечислять семь типов языка и знать, почему
typeof nullвозвращает'object'. - Объяснять, почему
0.1 + 0.2не равно0.3, и почему суммы в сети хранят целыми минимальными единицами. - Узнавать
NaNи проверять его черезNumber.isNaN, а не сравнением. - Писать шаблонные строки с подстановкой и объяснять каждый символ конструкции
${...}. - Помнить, что строки неизменяемы, и не терять результат вызова метода.
- Перечислять восемь falsy-значений и объяснять, почему
if (height)— плохая проверка на отсутствие. - Выбирать между
??и||по смыслу задачи. - Различать
nullиundefinedи знать идиомуvalue == null. - Предсказывать результат
'5' * 2и'5' + 2и объяснять, почему+— особенный. - Приводить внешние данные к числу через
Numberи проверять черезNumber.isFinite. - Писать аннотации там, где они нужны, и не писать там, где тип выводится.
- Читать номера ошибок TypeScript:
TS2322,TS2345,TS2554,TS18048,TS2339. - Описывать набор допустимых значений через union литеральных типов.
Углубиться
Почему typeof null === 'object'. В самой первой реализации JavaScript значения хранились парой «тег типа + данные», и тег 000 означал объект. Значение null представлялось нулевым указателем, у которого все биты нули, — то есть с тем же тегом. Исправить это предлагали не раз, но любая правка ломает существующий код, и предложение отклоняли.
Почему 0.1 + 0.2 даёт именно 0.30000000000000004. Число хранится в 64 битах: знак, 11 бит порядка и 52 бита мантиссы. 0.1 и 0.2 в двоичной записи периодические, их обрезают до 52 бит, и при сложении погрешности складываются. Ближайшее представимое число к точному результату оказывается чуть больше 0.3 — его и печатают. Тот же эффект в Java, Kotlin, C, Python: это свойство стандарта IEEE 754, а не языка.
Обёртки примитивов. Строка не объект, но 'привет'.length работает. Происходит вот что: при обращении к свойству движок временно оборачивает примитив в объект-обёртку (String, Number, Boolean), берёт свойство и обёртку выбрасывает. Из-за этого попытка что-нибудь записать в примитив молча ничего не делает — записали в обёртку, которая тут же исчезла.
Object.freeze против const. const защищает имя, Object.freeze(obj) — содержимое объекта, запрещая менять поля во время выполнения. В TypeScript похожего эффекта на этапе компиляции добиваются через as const и readonly, но это проверка компилятора, а не рантайма — она стирается при сборке.
Кодировка и длина строки. 'Привет'.length равно 6, но length считает не символы, а единицы кодировки UTF-16. Для эмодзи и некоторых иероглифов один видимый символ занимает две единицы, и length даст 2. Разбор строки на настоящие символы делают через итерацию: [...s] или Array.from(s).
Number.EPSILON. Для сравнения дробных чисел вместо a === b иногда используют Math.abs(a - b) < Number.EPSILON, где EPSILON — минимальная различимая разница. В денежных расчётах этот приём не спасает: правильное решение — целые числа минимальных единиц.
Дальше по теме:
- MDN: типы данных и структуры JavaScript — полный список типов от первоисточника
- MDN: Number — все методы и константы числа, включая
MAX_SAFE_INTEGER - TypeScript Handbook: Everyday Types — аннотации, union, псевдонимы типов
Сводная проверка по всей главе: 13 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Куда дальше: проверенные ресурсы
-
MDN: типы данных и структуры JavaScript — исчерпывающий список типов языка с примерами.
-
MDN: let — область видимости и временная мёртвая зона от первоисточника.
-
MDN: const — что именно фиксирует объявление константы.
-
MDN: Number — методы и константы числа, включая
MAX_SAFE_INTEGERиisFinite. -
MDN: NaN — почему
NaN !== NaNи как проверять правильно. -
MDN: строки и шаблонные литералы — подстановка, многострочность, экранирование.
-
MDN: приведение типов и равенство — полная таблица
==против===. -
MDN: falsy — точный список из восьми значений.
-
MDN: оператор ?? — чем отличается от
||. -
MDN: BigInt — целые произвольной длины и запрет на смешивание с
number. -
TypeScript Handbook: Everyday Types — аннотации, union, псевдонимы типов.
-
TypeScript: вывод типов — как компилятор догадывается о типе без аннотации.
-
TypeScript: справочник ошибок — что включает
strictи почемуstrictNullChecksменяет всё. -
Видео и материалы сообщества — ниже — ролики по теме и ссылки от студентов и преподавателей смотри в самом низу страницы.
Комментарии
Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.