Программирование на JavaScript и TypeScript: От основ до архитектуры

Этот курс охватывает путь от базового синтаксиса JavaScript до продвинутой типизации в TypeScript и принципов построения масштабируемых приложений. Студенты изучат современные стандарты языка, асинхронное программирование, ООП и ключевые архитектурные паттерны.

1. Основы JavaScript: Синтаксис, структуры данных и функции стандарта ES6+

Основы JavaScript: Синтаксис, структуры данных и функции стандарта ES6+

JavaScript прошел долгий путь от простого скриптового языка для оживления веб-страниц до мощного инструмента, на котором строятся сложные корпоративные системы, серверные приложения и мобильный софт. Чтобы писать качественный код на JavaScript (и, как следствие, на TypeScript), необходимо не просто знать синтаксис, но и понимать, как язык работает «под капотом» и какие возможности предоставляют современные стандарты ECMAScript (ES6+).

В этой статье мы разберем фундамент современного JavaScript: от правильного объявления переменных до элегантной работы со структурами данных.

Эволюция переменных: var, let и const

Долгое время единственным способом объявить переменную в JavaScript было ключевое слово var. Однако оно имело ряд недостатков, связанных с областью видимости (scope) и поднятием (hoisting), что часто приводило к трудноуловимым ошибкам.

С приходом стандарта ES6 (ECMAScript 2015) мы получили два новых инструмента: let и const. Это стало стандартом индустрии, и использование var в современном коде считается дурным тоном.

Область видимости (Scope)

Ключевое отличие let и const от var — это блочная область видимости. Переменная, объявленная внутри блока кода (ограниченного фигурными скобками { ... }), не видна за его пределами.

!Блочная область видимости let/const против функциональной области видимости var

Константы и мутабельность

const создает константу — переменную, которую нельзя переназначить. Однако здесь есть важный нюанс: если в константу записан объект или массив, вы можете менять его содержимое.

Это происходит потому, что const защищает от изменения только саму ссылку на объект в памяти, но не данные внутри этого объекта.

Типы данных: Примитивы и Объекты

В JavaScript существует динамическая типизация. Это означает, что одна и та же переменная может хранить данные разных типов в разное время. Все типы данных делятся на две большие категории: примитивы и объекты.

Примитивные типы

Примитивы передаются по значению. Это значит, что когда вы присваиваете одну переменную другой, создается независимая копия данных.

К примитивам относятся:

  • String (строка)
  • Number (число) — включает в себя и целые, и дробные числа
  • Boolean (логический тип) — true или false
  • Null — специальное значение, означающее «ничего» или «пусто»
  • Undefined — значение переменной, которая была объявлена, но не инициализирована
  • Symbol — уникальный идентификатор (используется редко)
  • BigInt — для работы с очень большими целыми числами
  • Ссылочные типы (Объекты)

    Объекты (включая массивы и функции) передаются по ссылке. Когда вы копируете объект, вы копируете не сами данные, а адрес в памяти, где эти данные лежат.

    !Принцип работы ссылочных типов данных

    Операторы сравнения

    В JavaScript есть два вида операторов равенства:

    * == (нестрогое равенство): пытается привести типы к общему знаменателю перед сравнением. Часто приводит к неожиданным результатам (например, 0 == '0' вернет true). * === (строгое равенство): сравнивает и значение, и тип данных. 0 === '0' вернет false.

    Золотое правило: Всегда используйте ===, если у вас нет веской причины делать иначе.

    Функции в стиле ES6

    Функции — это сердце JavaScript. В ES6 появился новый синтаксис — стрелочные функции (Arrow Functions). Они делают код лаконичнее и имеют особенности в работе с контекстом this (о чем мы поговорим в будущих статьях про ООП).

    Синтаксис

    Классическое объявление:

    Стрелочная функция:

    Если тело функции состоит из одной строки, можно убрать фигурные скобки и слово return — возврат значения произойдет автоматически (неявный возврат):

    Параметры по умолчанию

    Раньше для установки значений по умолчанию приходилось писать дополнительные проверки внутри функции. Теперь это делается прямо в объявлении аргументов:

    javascript const user = { id: 42, username: 'jdoe', email: 'john@example.com' };

    // Старый способ const id = user.id; const username = user.username;

    // Новый способ (деструктуризация) const { id, username } = user; javascript const coordinates = [10, 20]; const [x, y] = coordinates; console.log(x); // 10 javascript const parts = ['плечи', 'колени']; const body = ['голова', ...parts, 'пальцы']; // ['голова', 'плечи', 'колени', 'пальцы']

    const baseUser = { name: 'Max' }; const fullUser = { ...baseUser, age: 30 }; // { name: 'Max', age: 30 } javascript const sumAll = (...numbers) => { // numbers теперь массив всех переданных аргументов return numbers.reduce((acc, current) => acc + current, 0); };

    sumAll(1, 2, 3, 4); // 10 javascript const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); // [2, 4, 6] javascript const numbers = [1, 2, 3, 4, 5]; const evens = numbers.filter(num => num % 2 === 0); // [2, 4] javascript const numbers = [10, 20, 30]; const total = numbers.reduce((sum, current) => sum + current, 0); // 60 ``

    !Визуализация процесса обработки данных методами filter, map и reduce

    Итоги

    Мы рассмотрели ключевые особенности синтаксиса ES6+, которые являются стандартом для современной разработки.

  • Используйте let и const вместо var для предсказуемого управления областью видимости. Предпочитайте const по умолчанию.
  • Помните разницу между примитивами (копируются по значению) и объектами (копируются по ссылке).
  • Стрелочные функции делают код чище, особенно при работе с колбэками.
  • Деструктуризация и оператор Spread (...) значительно упрощают работу со сложными структурами данных.
  • Методы массивов map, filter и reduce` позволяют писать декларативный и читаемый код обработки данных.
  • 2. Глубокое погружение: Асинхронность, Event Loop и объектно-ориентированное программирование в JS

    Глубокое погружение: Асинхронность, Event Loop и объектно-ориентированное программирование в JS

    После освоения синтаксиса и базовых структур данных пришло время заглянуть под капот JavaScript. Многие новички (и даже опытные разработчики) пишут код, который работает, но не до конца понимают почему он работает именно так. Особенно это касается моментов, когда порядок выполнения строк кода не совпадает с их порядком в файле.

    В этой статье мы разберем архитектуру исполнения кода (Event Loop), научимся управлять временем с помощью асинхронности и структурировать сложные приложения, используя объектно-ориентированный подход.

    Однопоточность и Event Loop

    JavaScript — это однопоточный язык программирования. Это означает, что у него есть только один стек вызовов (Call Stack), и он может выполнять только одну операцию в конкретный момент времени. Если вы запустите бесконечный цикл while(true), вы «заморозите» всю вкладку браузера, потому что поток будет занят и не сможет реагировать на клики или отрисовку интерфейса.

    Однако, мы постоянно делаем сетевые запросы, ставим таймеры и обрабатываем события кликов, не блокируя интерфейс. Как это возможно? Ответ кроется в механизме, называемом Event Loop (Цикл событий).

    Архитектура исполнения

    Среда выполнения JavaScript (например, браузер или Node.js) состоит из нескольких частей:

  • Call Stack (Стек вызовов): Здесь выполняется текущий синхронный код. При вызове функции она попадает в стек, при возврате значения — удаляется из него.
  • Web APIs (в браузере): Это интерфейсы, предоставляемые браузером (setTimeout, fetch, DOM events). Они работают параллельно основному потоку JS.
  • Callback Queue (Очередь задач): Сюда попадают колбэки от Web APIs (например, функция внутри setTimeout), когда они готовы к выполнению.
  • Event Loop: Это бесконечный цикл, который следит за стеком и очередью. Если стек пуст, он берет первую задачу из очереди и помещает её в стек.
  • !Взаимодействие стека вызовов, Web API и очереди задач

    Пример работы

    Рассмотрим классический пример, который часто сбивает с толку:

    Результат выполнения:

  • Первый
  • Третий
  • Второй
  • Почему так происходит? Даже с задержкой в 0 миллисекунд, setTimeout не выполняет функцию сразу. Он передает её в Web API, который тут же возвращает колбэк в очередь задач (Callback Queue). Но Event Loop не переложит задачу из очереди в стек, пока стек не станет пустым. Поэтому сначала выполнится весь синхронный код (console.log('Третий')), и только потом — асинхронный.

    Микрозадачи и Макрозадачи

    Внутри Event Loop существует приоритетность. Очереди делятся на два типа:

    * Macrotasks (Макрозадачи): setTimeout, setInterval, события UI. * Microtasks (Микрозадачи): Promise, queueMicrotask.

    Правило: После выполнения каждой макрозадачи (или синхронного кода), движок JS сначала выполняет ВСЕ микрозадачи из очереди, и только потом переходит к следующей макрозадаче.

    Порядок вывода: 1, 4, 3, 2.

  • 1 и 4 — синхронный код (сразу в стеке).
  • 3 — микрозадача (Promise), имеет приоритет.
  • 2 — макрозадача (setTimeout), выполняется в последнюю очередь.
  • Асинхронность: От Callbacks к Async/Await

    Промисы (Promises)

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

  • Pending (Ожидание): Исходное состояние.
  • Fulfilled (Выполнено): Операция завершена успешно.
  • Rejected (Отклонено): Произошла ошибка.
  • Промисы позволили избавиться от «Ада колбэков» (Callback Hell), когда вложенность функций делала код нечитаемым.

    Async / Await

    В стандарте ES2017 (ES8) появился синтаксис async/await, который является «синтаксическим сахаром» над промисами. Он позволяет писать асинхронный код так, как будто он синхронный.

    * Ключевое слово async перед функцией гарантирует, что она вернет промис. * Ключевое слово await заставляет интерпретатор ждать выполнения промиса, прежде чем идти дальше.

    Это самый современный и читаемый способ работы с асинхронностью в JavaScript и TypeScript.

    Объектно-Ориентированное Программирование (ООП)

    JavaScript — мультипарадигменный язык. Он поддерживает и функциональное, и объектно-ориентированное программирование. Однако реализация ООП в JS отличается от классических языков вроде Java или C#.

    Прототипное наследование

    В основе JS лежит прототипное наследование. У каждого объекта есть скрытая ссылка на другой объект, называемый его прототипом (__proto__). Когда вы пытаетесь прочитать свойство, которого нет у объекта, JS ищет его в прототипе, затем в прототипе прототипа и так далее, пока не дойдет до null.

    !Механизм поиска свойств по цепочке прототипов

    Классы (ES6+)

    Хотя под капотом работают прототипы, современный стандарт предоставляет удобный синтаксис class, понятный разработчикам из других языков.

    Контекст this

    Одна из самых сложных тем в JS — это ключевое слово this. Его значение зависит не от того, где функция была объявлена, а от того, как она была вызвана.

  • В глобальной области: this — это глобальный объект (window в браузере).
  • В методе объекта: this — это сам объект перед точкой.
  • В стрелочной функции: this берется из внешнего (лексического) окружения. У стрелочных функций нет своего this.
  • Пример потери контекста:

    Здесь user.greet передается как ссылка на функцию, и setTimeout вызывает её без контекста объекта user. Решение — использовать стрелочную функцию или метод bind.

    Итоги

    Мы разобрали фундаментальные механизмы, которые делают JavaScript мощным инструментом для создания сложных приложений.

  • Event Loop позволяет однопоточному JavaScript выполнять асинхронные операции (таймеры, запросы), не блокируя интерфейс.
  • Микрозадачи (Promises) имеют приоритет перед макрозадачами (setTimeout). Понимание этого порядка критично для предсказуемости кода.
  • Async/Await — современный стандарт работы с асинхронностью, делающий код чище и понятнее.
  • Классы в JS — это удобная обертка над прототипным наследованием, позволяющая строить архитектуру приложения в стиле ООП.
  • Контекст this динамичен и зависит от места вызова, но стрелочные функции позволяют зафиксировать его лексически.