Перейти к основному содержимому
Блокчейн20ГЛАВА 20Значения и переменные: числа, строки, типы42sumlast

Значения и переменные: числа, строки, типы

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Предыдущая глава объяснила, чем TypeScript отличается от JavaScript, но обошла стороной вопрос, с которого на самом деле начинается программирование: что вообще лежит в переменной и по каким правилам оно себя ведёт.

Это не мелочь и не разминка перед «настоящими темами». Почти все ошибки, которые студент ловит на первом чемпионате, живут именно здесь: комиссия посчиталась как NaN, потому что где-то к числу прибавили строку; сумма перевода получилась 0.30000000000000004; сравнение адресов через == сработало не так, как ожидалось; поле height оказалось undefined, и программа упала через три функции после места ошибки.

Поэтому здесь мы разбираем всё максимально медленно: что такое значение, что такое переменная, чем let отличается от const, какие типы вообще существуют в языке, почему число в JavaScript одно на всё, и что происходит, когда типы не совпадают. Каждый вывод в этой главе снят с живого запуска Node.js 24 и компилятора TypeScript 6 — ни одного «примерно так должно быть».

Значение и переменная — это два разных предмета​

Первое, что нужно развести в голове, — значение и переменная.

Значение — это данные. Число 5. Строка 'Олег'. Логическое true. Значение само по себе ни в чём не хранится, оно просто есть: когда ты пишешь в коде 5, это не «переменная со значением пять», это само число пять, записанное прямо в тексте программы. Такая запись называется литерал — значение, записанное буквально.

Переменная — это имя, привязанное к значению. Не коробка, в которую значение положили, а именно ярлык, который на значение навесили. Разница проявляется сразу, как только одно и то же значение получает два имени.

const a = 5;
const b = a; // b — второй ярлык, но не на «то же место», а на то же значение

Правильная картинка для первых недель: слева имя, справа значение, между ними знак =, который читается не «равно», а «пусть отныне это имя указывает вот на это». Знак = в программировании — не утверждение о равенстве, а команда. Именно поэтому строка x = x + 1 не является абсурдом: справа вычисляется старое значение плюс один, слева имя x переприкрепляется к результату.

ИМЯ — ЯРЛЫК, А НЕ КОРОБКАкак кажетсяheight152340коробка с числом внутриприсваивание меняетсодержимое коробкикак на самом деле152340значениеheightlastдва ярлыка на одном значенииприсваивание перевешивает ярлык,а не трогает значение= читается «получает», а не «равно»
Значение живёт само по себе, имя — это ярлык, привязанный к значению. Присваивание не двигает значение, оно перевешивает ярлык.
Совет

Читай = вслух как «получает» или «становится». 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 — движок вообще не смог разобрать текст, и до выполнения дело не дошло.

Отвечено вопросов: 0 из 4
Что на самом деле запрещает const?
ReferenceError: height is not defined. О чём это сообщение?
Почему по умолчанию советуют const, а не let?
Знак = в строке let total = total + 1 читается как…
Отвечено верно: 0 из 4

Какие типы вообще бывают: живой вывод 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 на балансе.

ОДИН ТИП NUMBER НА ВСЁnumber = IEEE 754 doubleцелые до 2^532 + 2 === 4trueарифметика точнадроби0.1 + 0.20.30000000000000004хвост теряетсяза 2^539007199254740992+ 1 === то же самоешаг через односуммы монет считают в целых или через bigint, а не дробями
Один тип number на всё: в середине диапазона арифметика точна, у дробей теряется хвост, за границей безопасных целых числа начинают прыгать через одно.

Ещё три вывода живого запуска, которые надо знать наизусть:

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(); // так правильно: сохранили результат

Ошибка «вызвал метод и забыл сохранить результат» встречается у всех и не даёт никакой ошибки при выполнении: код просто ничего не делает.

Отвечено вопросов: 0 из 5
Почему 0.1 + 0.2 даёт 0.30000000000000004?
Как в блокчейне обходят проблему точности сумм?
Что возвращает выражение (0.1 + 0.2).toFixed(2)?
Как правильно проверить, что в переменной оказался NaN?
Что напечатает код: const s = "привет"; s.toUpperCase(); console.log(s);
Отвечено верно: 0 из 5

Логическое значение, truthy и falsy​

Тип boolean состоит ровно из двух значений: true и false. Но в условиях язык принимает не только их — он умеет превращать в логическое любое значение. Значения, которые превращаются в false, называют falsy, остальные — truthy.

Falsy-значений ровно восемь, и их проще выучить наизусть, чем каждый раз проверять:

false 0 -0 0n '' null undefined NaN

Всё остальное truthy. В том числе то, что интуиция подсказывает считать пустым:

if ([]) { /* сюда зайдёт: пустой массив truthy */ }
if ({}) { /* сюда зайдёт: пустой объект truthy */ }
if ('0') { /* сюда зайдёт: непустая строка truthy, даже если в ней ноль */ }
ВОСЕМЬ FALSY И ВСЁ ОСТАЛЬНОЕfalsy — условие ложноfalse0-00n""nullundefinedNaNсписок закрытый: больше ничего нетtruthy — условие истинно[] пустой массив{} пустой объект"0" строка с нулём"false" строка-1 любое число, кроме 0function () {}здесь и ломается интуиция?? проверяет только null и undefined, а два знака | подряд — всю левую колонку
Восемь falsy-значений и всё остальное. Пустой массив, пустой объект и строка '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(); // здесь компилятор уже спокоен
}
ДВЕ РАЗНЫЕ ПУСТОТЫundefinedвозникает само собойобъявили без значенияполя объекта нетаргумент не передалифункция без returnnullкладут руками«значения нет намеренно»ответ пришёл пустымполе сброшено явносам собой не появитсяnull === undefined → falsenull == undefined → true
undefined возникает сам собой там, где значение не положили; null кладут руками. Строгое сравнение их различает, нестрогое — считает одинаковыми.

Без проверки в чистом 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', точное значение, и все поля станут только для чтения. Это то, что нужно для настроек: случайное изменение поля перестанет компилироваться.

Отвечено вопросов: 0 из 4
Где аннотация типа обязательна, а где избыточна?
Чем отличаются ошибки TS2322 и TS2345?
Что даёт запись type TxStatus = "unknown" | "unconfirmed" | "confirmed"?
Зачем нужен ?? вместо ||?
Отвечено верно: 0 из 4

Имена переменных: правила языка и правила людей​

Правила языка короткие. Имя может состоять из букв, цифр, знаков _ и $, но не может начинаться с цифры. Регистр важен: 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 functionh.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;
клавиша ``клавиша 11клавиша 22клавиша 33клавиша 44клавиша 55клавиша 66клавиша 77клавиша 88клавиша 99клавиша 00клавиша --клавиша ==клавиша ⌫⌫клавиша TabTabклавиша QQклавиша WWклавиша EEклавиша RRклавиша TTклавиша YYклавиша UUклавиша IIклавиша OOклавиша PPклавиша [[клавиша ]]клавиша \\клавиша Caps LockCaps Lockклавиша AAклавиша SSклавиша DDклавиша FFклавиша GGклавиша HHклавиша JJклавиша KKклавиша LLклавиша ;;клавиша ''клавиша EnterEnterклавиша ShiftShiftклавиша ZZклавиша XXклавиша CCклавиша VVклавиша BBклавиша NNклавиша MMклавиша ,,клавиша ..клавиша //клавиша ShiftShiftклавиша CtrlCtrlклавиша WinWinклавиша AltAltклавиша ПробелПробелклавиша AltAltклавиша WinWinклавиша MenuMenuклавиша CtrlCtrl
следующая: C — средний левой руки

Символы этой главы​

ЗнакНазваниеЧто делает
=присваиваниепривязывает имя к значению справа
=== / !==строгое сравнениесравнивает без приведения типов; рабочий вариант по умолчанию
== / !=нестрогое сравнениеприводит типы перед сравнением; осознанно пишут только как == null
:аннотация типа«это имя имеет такой тип»; только TypeScript
``объединение типов
`обратная кавычкашаблонная строка с подстановкой
${ }интерполяцияподставляет значение выражения в шаблонную строку
??нулевое слияниезапасное значение только для null и undefined
два знака | подрядлогическое ИЛИзапасное значение для любого falsy
?.опциональная цепочкаобращение к полю, которое может отсутствовать, без падения
nсуффикс bigint10n — целое произвольной длины
;точка с запятойконец инструкции
//комментарийвсё до конца строки игнорируется

Что должен уметь​

  • Объяснять разницу между значением и переменной и читать = как «получает», а не «равно».
  • Выбирать между 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 — минимальная различимая разница. В денежных расчётах этот приём не спасает: правильное решение — целые числа минимальных единиц.

Дальше по теме:

Сводная проверка по всей главе: 13 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоПеременные в JavaScript. Константы. Ключевые слова let, const, varФрілансер по життю
ВидеоПеременные в JavaScript (var, let, const), строгий режим (use strict). Как называть переменные?Александр Ламков — Friendly Frontend
Видео#2. Уроки по JavaScript для НАЧИНАЮЩИХ (const, let, var, типы данных, typeof)Какие-то уроки [Frontend]
ВидеоУроки JavaScript | Урок №4 | Переменные, константы, типы данных. Var, let, const, hoistingAlex Web
ВидеоПеременные в JavaScript | let, const и var | Курс по JavaScript №2(не) AlixHatsu

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.