Веб-разработка
JavaScript: основы языка
1995 год. Netscape. Брендан Эйх пишет JavaScript за 10 дней - язык, который должен был быть «скриптованием для не-программистов». Тридцать лет спустя: 98% всех сайтов в мире, Node.js обрабатывает запросы Netflix со скоростью 70 тысяч в секунду. `typeof null === 'object'` - баг, пережившей браузерные войны. Понимание «почему» превращает ловушки JS в суперсилу.
- **Netflix** перешёл на Node.js и сократил время запуска приложения на 70% - благодаря event loop и неблокирующему I/O
- **React hooks** (`useState`, `useEffect`) работают на замыканиях - каждый рендер создаёт новое замыкание с текущим состоянием
- **Cloudflare Workers** выполняют JavaScript на edge-серверах по всему миру с cold start меньше 5мс - быстрее Lambda
Netscape, 1995: язык написанный за 10 дней
В мае 1995 года Брендан Эйх получил задачу от Netscape: создать «язык для склейки» HTML-элементов в браузере. Дедлайн - 10 дней. Он создал прототип, взяв синтаксис из Java, функции первого класса из Scheme и прототипное наследование из Self. typeof null === "object" - артефакт той спешки. Язык назвали Mocha, потом LiveScript, потом - в маркетинговых целях, ничего общего с Java - JavaScript. В 1997 ECMAScript-стандарт. В 2009 Node.js. В 2015 ES6 с классами. В 2023 - 98% сайтов мира.
Предварительные знания
Типы данных
**JavaScript - язык с динамической типизацией.** Переменная может сначала хранить число, потом строку, потом объект. Это даёт гибкость, но и порождает коварные баги. Понимание системы типов - первый шаг к предсказуемому коду.
В JavaScript **7 примитивных типов** и **1 ссылочный** (object). Примитивы - неизменяемые значения, которые сравниваются по значению. Объекты - изменяемые структуры, которые сравниваются по ссылке.
| Тип | Примеры | typeof |
|---|---|---|
| string | "hello", 'world', `template` | "string" |
| number | 42, 3.14, NaN, Infinity | "number" |
| boolean | true, false | "boolean" |
| null | null | "object" (баг с 1995 года!) |
| undefined | undefined | "undefined" |
| symbol | Symbol('id') | "symbol" |
| bigint | 9007199254740991n | "bigint" |
| object | {}, [], function(){}, new Date() | "object" или "function" |
**`typeof null === "object"`** - это не фича, это баг из первой версии JavaScript (1995). Брендан Эйх написал язык за 10 дней, и этот баг сохранился ради обратной совместимости. Для проверки на null используется `value === null`.
**Правило на всю карьеру:** всегда использовать `===` вместо `==`. Оператор `==` выполняет неявное приведение типов по сложным правилам, которые не знает наизусть ни один опытный разработчик. `===` сравнивает без приведения - предсказуемо и безопасно.
Что выведет `console.log(typeof null)`?
Замыкания
**Замыкание - одна из самых важных концепций JavaScript.** Каждый раз, когда используется callback, обработчик события или паттерн модуля - это замыкание. Суть: **функция запоминает переменные из места, где была создана**, даже если выполняется в совершенно другом контексте.
**Lexical Environment** - объект, который движок JavaScript создаёт при каждом вызове функции. Хранит локальные переменные и ссылку на внешнее окружение. Замыкание - это функция плюс ссылка на лексическое окружение, в котором она была создана.
**Классическая ловушка с циклами:** `for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }` выведет `3, 3, 3` - все функции замкнули одну переменную `i`. Решение: использовать `let` вместо `var` - `let` создаёт новое окружение на каждой итерации.
Замыкания - фундамент для React hooks (`useState`, `useEffect`), Express middleware, Redux и практически любого JavaScript-паттерна. Понимание замыканий = понимание того, как все эти инструменты работают изнутри.
Что выведет код: `for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }` ?
Прототипы
**В JavaScript нет классов в классическом смысле.** Ключевое слово `class` (ES6) - это синтаксический сахар над прототипным наследованием. У каждого объекта есть скрытая ссылка `[[Prototype]]` на другой объект. Когда обращаются к свойству, которого у объекта нет, JavaScript ищет его по **цепочке прототипов**.
**`__proto__`** - геттер/сеттер для доступа к `[[Prototype]]` объекта (нестандартный, но повсеместно поддерживаемый). **`.prototype`** - свойство функций-конструкторов, которое становится `[[Prototype]]` для объектов, созданных через `new`. Не путать: `__proto__` есть у каждого объекта, `.prototype` - только у функций.
**Частый вопрос на интервью:** «В чём разница между `class` и функцией-конструктором?» Ответ: `class` строже - нельзя вызвать без `new`, методы не перечисляемые, всегда в strict mode. Но механизм наследования идентичен - та же цепочка прототипов.
Что произойдёт при обращении к свойству, которого нет ни у объекта, ни у его прототипов?
Event Loop
**JavaScript выполняет код в одном потоке.** Но при этом Node.js обрабатывает тысячи параллельных HTTP-запросов и не замораживает UI в браузере. Как? Через **event loop** - механизм, координирующий выполнение синхронного кода, callbacks, promises и I/O-операций. Netflix перешёл на Node.js и сократил время запуска приложения на 70% - именно благодаря этой архитектуре.
**Ключевое правило:** микро-задачи (Promise, queueMicrotask, MutationObserver) имеют приоритет над макро-задачами (setTimeout, события). Все микро-задачи выполняются перед следующей макро-задачей. Значит `Promise.resolve().then(fn)` выполнится раньше `setTimeout(fn, 0)`.
**`setTimeout(fn, 0)` не означает «выполнить через 0мс».** Это означает «добавить в очередь задач после текущего стека и всех микро-задач». Реальная задержка - минимум 4мс (ограничение спецификации HTML5), плюс время ожидания в очереди.
В **Node.js** есть дополнительная фаза: `process.nextTick()` выполняется даже раньше Promises. Приоритет: Call Stack - process.nextTick - Microtasks (Promise) - Macrotasks (setTimeout). В браузере nextTick не существует.
JavaScript однопоточный, значит он медленный и не подходит для высоконагруженных серверов
Однопоточность JavaScript компенсируется event loop и неблокирующим I/O. Node.js обрабатывает тысячи параллельных соединений в одном потоке, потому что 90% времени сервер ожидает I/O (БД, сеть, файлы), а не вычисляет
Для I/O-bound задач (веб-серверы, API, real-time приложения) event loop эффективнее модели «один поток на запрос» (Java/PHP). Netflix, LinkedIn, PayPal перешли на Node.js именно ради производительности. Для CPU-bound задач (рендеринг, ML) JavaScript действительно уступает - но для этого есть Worker Threads
В каком порядке выполнятся: `console.log('A')`, `setTimeout(() => console.log('B'), 0)`, `Promise.resolve().then(() => console.log('C'))`?
Ключевые идеи
- **7 примитивов + object.** Примитивы - по значению, объекты - по ссылке. `===` вместо `==` всегда. `typeof null === 'object'` - баг с 1995 года
- **Замыкание = функция + лексическое окружение.** Счётчик, модульный паттерн, частичное применение. `let` вместо `var` в циклах. Замыкания - фундамент React hooks
- **Цепочка прототипов - наследование в JavaScript.** `class` - сахар над прототипами. `Object.create()` для прямого создания цепочки. Свойство ищется по цепочке до null
- **Event loop: microtask > macrotask.** Promise.then выполняется раньше setTimeout. async/await не блокирует - отдаёт управление event loop. Single-thread + async I/O = идеально для серверов
Связанные темы
JavaScript - фундамент всей экосистемы веб-разработки:
- CSS: от каскада до Grid — JavaScript управляет стилями через classList, style и CSS Custom Properties
- HTML и семантическая разметка — JavaScript взаимодействует с HTML через DOM API (document.querySelector, createElement)
Вопросы для размышления
- Откройте консоль браузера и выполните: `[] == ![]`. Результат - true. Можно ли объяснить, какие приведения типов к этому привели?
- Напишите функцию `once(fn)`, которая разрешает вызвать fn только один раз, а при последующих вызовах возвращает первый результат. Какая концепция из этого урока - ключевая?
- Представьте: Node.js сервер обрабатывает запрос с тяжёлым парсингом JSON (100 MB). Как будет вести себя event loop и что произойдёт с другими запросами?
Связанные уроки
- web-02 — JavaScript манипулирует CSS и DOM из предыдущего урока
- web-04 — React, TypeScript и современные фреймворки строятся поверх основ JS
- web-05 — Async/await и Promises расширяют event loop из этого урока
- se-05 — Event loop - паттерн Observer в disguise на уровне рантайма
- alg-01 — Prototype chain lookup - линейный поиск, стоит понимать сложность
- mob-03 — SwiftUI @State и React hooks - одна декларативная идея на разных платформах
- comp-33-v8