JavaScript: синтаксис, типы, функции
В формах из прошлого урока уже мелькал JS - addEventListener, fetch, e.preventDefault(). Сейчас разберём, что это за язык, какие у него грабли и зачем он бэкендеру.
JS - это язык, который браузер исполняет на стороне клиента. На бэкенде ты пишешь API на Go/PHP. На фронте JS дёргает твой API, рисует интерфейс, валидирует формы. Знание JS на уровне «прочитал и понял» - обязательный минимум для backend-разработчика. Без него ты не дебагнешь интеграцию с фронтом, не напишешь скрипт автоматизации, не разберёшь чужой fetch.
Где запускать JS
Три способа на старте - выбирай удобный:
1. DevTools → Console - открой любую страницу, F12 → вкладка Console.
Печатай команды, видишь результат. Идеально для экспериментов.
2. .html файл со скриптом:
<script>console.log('Привет');</script>
Открой файл в браузере, открой DevTools → Console.
3. Node.js (изучим в js-node) - JS без браузера.
Первые уроки будем работать в DevTools → Console. Бэкендер всё равно проводит там половину времени дебага.
Переменные: let и const
let count = 0; // можно переприсваивать
count = 5; // OK
const PI = 3.14; // нельзя переприсваивать
// PI = 3.15; // TypeError: Assignment to constant variable
let - изменяемая переменная. const - неизменяемая. По умолчанию пиши const, переключайся на let только когда точно нужно переприсваивать (счётчик, флаг, аккумулятор).
const для объекта - не «всё запрещено»
Главная грабля для бэкендера, пришедшего из языков со строгой иммутабельностью:
const user = { name: 'Иван' };
user.name = 'Пётр'; // OK! Объект тот же, поменялось поле
user.age = 30; // OK! Добавили новое поле
// user = {}; // TypeError: переприсваивание самой ссылки запрещено
const запрещает менять саму ссылку на объект, но не запрещает менять содержимое. Если нужна реальная иммутабельность - Object.freeze(user).
Типы
JS - динамически типизированный язык. Тип определяется значением, а не объявлением:
let x = 42; // number
x = 'hello'; // string - JS не возражает
x = true; // boolean
x = null; // null
x = undefined; // undefined
x = { a: 1 }; // object
x = [1, 2, 3]; // тоже object (массив - это объект)
console.log(typeof x); // "object"
Семь типов: number, string, boolean, null, undefined, object (включая массивы и функции), symbol (редко). typeof null === 'object' - исторический баг JS, который никто уже не исправит.
number - без int и float
В JS нет отдельного целого и дробного - только number:
1 + 1 // 2
1.5 * 2 // 3
1 / 0 // Infinity (не ошибка!)
0 / 0 // NaN (Not a Number)
0.1 + 0.2 // 0.30000000000000004 - float, IEEE 754
Если нужна точность для денег - отдельная библиотека (decimal.js) или хранить копейки целыми числами.
string - двойные, одинарные, обратные
const a = 'одинарные';
const b = "двойные";
const c = `обратные - поддерживают подстановку: ${a}`;
// → "обратные - поддерживают подстановку: одинарные"
Обратные кавычки (template literals) - для строк с подстановкой ${...} и для многострочных строк. По умолчанию пиши 'одинарные', если не нужна подстановка.
null vs undefined
let a; // объявлена, не присвоена → undefined
let b = null; // явно «значения нет»
console.log(typeof a); // "undefined"
console.log(typeof b); // "object" (опять исторический баг)
Конвенция: undefined - «значения не присвоили», null - «специально поставили пусто». На практике многие API возвращают то одно, то другое. Проверяй обе ситуации одной идиомой: if (value == null) сравнит и с null, и с undefined.
Главная грабля: == vs ===
'1' == 1 // true - приведение типов
'1' === 1 // false - строгое сравнение
null == undefined // true
null === undefined // false
[] == false // true - wtf?
[] === false // false
== сначала приводит типы, потом сравнивает. Это источник тонн багов. Всегда пиши === и !==. Единственное полезное исключение - == null для проверки «null или undefined» одним выражением.
Truthy и falsy
Многие операторы (if, &&, ||) принуждают значение к boolean. Шесть значений падают в false, всё остальное - в true:
// Falsy:
false, 0, -0, '', null, undefined, NaN
// Всё остальное truthy, включая:
'0' // строка из символа '0' - truthy!
[] // пустой массив - truthy
{} // пустой объект - truthy
'false' // строка - truthy
Это даёт компактный код:
const name = userInput || 'Аноним';
// если userInput - пустая строка, undefined, null - возьмём 'Аноним'
Но и грабли:
const count = req.query.count || 10;
// если count === 0 (а это валидное число!), возьмём 10 → баг
С 2020-х есть ?? (nullish coalescing) - fallback только для null/undefined:
const count = req.query.count ?? 10;
// 0 останется 0, fallback только при null/undefined
Массивы и объекты
// Массивы
const arr = [1, 2, 3];
arr.length; // 3
arr[0]; // 1
arr.push(4); // [1, 2, 3, 4]
arr.includes(2); // true
arr.map(x => x * 2); // [2, 4, 6, 8]
arr.filter(x => x > 2);// [3, 4]
// Объекты
const user = {
name: 'Иван',
age: 30,
greet() { return 'Привет, ' + this.name; } // метод
};
user.name; // 'Иван'
user['age']; // 30 - синтаксис скобок, для динамических ключей
user.greet(); // 'Привет, Иван'
delete user.age; // удалить поле
Деструктуризация
Достать поля одним выражением:
const user = { name: 'Иван', age: 30, email: 'i@m.ru' };
const { name, age } = user;
console.log(name, age); // "Иван" 30
// С переименованием:
const { name: userName } = user;
// userName === 'Иван'
// Массивы:
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3, 4, 5]
Spread (распыление)
// Скопировать массив:
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// Скопировать объект:
const user = { name: 'Иван', age: 30 };
const updated = { ...user, age: 31 }; // { name: 'Иван', age: 31 }
Spread - иммутабельный путь обновлять объекты. В React пригодится, но и в обычном фронте удобно.
Функции
Два синтаксиса:
// Обычная функция
function add(a, b) {
return a + b;
}
// Стрелочная (arrow)
const add2 = (a, b) => a + b;
// Стрелочная с телом
const add3 = (a, b) => {
const result = a + b;
return result;
};
Стрелочные функции короче и решают проблему this (про это - на отдельном уроке, если зайдёт). Для бэкендера правило простое: пиши стрелочные везде, кроме методов внутри объекта.
Параметры по умолчанию
function greet(name = 'друг', greeting = 'Привет') {
return `${greeting}, ${name}!`;
}
greet(); // "Привет, друг!"
greet('Иван'); // "Привет, Иван!"
greet('Иван', 'Здарова'); // "Здарова, Иван!"
Rest-параметры
Сколько угодно аргументов в один массив:
function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
Замыкания на минимуме
Функция, объявленная внутри другой функции, помнит переменные внешней:
function makeCounter() {
let count = 0;
return () => ++count;
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
counter - стрелочная функция, которая «помнит» count из makeCounter. Это и есть замыкание. На практике встречается каждый день: setTimeout, обработчики событий, фабрики функций - везде замыкания.
Грабли, которые экономят часы дебага
1. Скобки при возврате объекта из стрелки
const wrong = () => { name: 'Иван' }; // вернёт undefined!
const right = () => ({ name: 'Иван' }); // обернули в круглые скобки
Без круглых скобок JS думает, что { ... } - это тело функции, а name: - метка.
2. === для NaN не работает
NaN === NaN // false! Единственное значение, не равное самому себе
Number.isNaN(NaN) // true - правильная проверка
3. Объекты сравниваются по ссылке
{ a: 1 } === { a: 1 } // false - это два разных объекта в памяти
const x = { a: 1 };
x === x // true - та же ссылка
Для сравнения по содержимому нужна функция или JSON.stringify(a) === JSON.stringify(b) (хрупкий способ, но иногда сходит).
4. for...in vs for...of
const arr = ['a', 'b', 'c'];
for (const i in arr) console.log(i); // '0', '1', '2' - индексы (строки!)
for (const v of arr) console.log(v); // 'a', 'b', 'c' - значения
Для массивов всегда for...of. for...in - для объектов, и то редко.
Что пропустили намеренно
В реальном JS-коде встретишь много того, что в этом уроке нет: классы, prototypes, this в его странных формах, async-итераторы, генераторы, символы, прокси. На уровне «прочитал и понял» это не критично - достаточно знать, что они есть, и гуглить по необходимости. Гнать тебя через всю спецификацию ES2020 на старте - пустая трата времени.
Мини-задание
- Открой DevTools → Console на любой странице
- Напиши
const user = { name: 'Иван', age: 30 };и достаньnameчерез деструктуризацию - Создай стрелочную функцию, которая принимает массив чисел и возвращает их сумму через
reduce - Объясни себе, почему
0.1 + 0.2 !== 0.3 - Найди в чужом фронт-коде (например, в твоём собственном фронте) одно использование
===и одно -===после??. Понял ли что они делают?
На следующем уроке начнём работать с DOM - изменять страницу из JS, ловить клики и сабмиты форм без перезагрузки.