JavaScript под микроскопом: 5 механизмов, которые ломают продакшен

JavaScript кажется простым до первого серьёзного проекта. Потом внезапно появляются «магические» баги, странное поведение this, гонки в асинхронном коде и утечки памяти.

Разберём 5 механизмов языка, которые обязательно нужно понимать, если вы пишете что-то сложнее лендинга.


1. Closure — не просто «функция внутри функции»

Замыкания — фундамент JS. Но их побочный эффект — удержание ссылок в памяти.

function createHeavyObject() {
  const bigData = new Array(1_000_000).fill("data");

  return function () {
    console.log("Working...");
  };
}

const fn = createHeavyObject();

Даже если bigData не используется во внутренней функции, оно останется в памяти, пока существует fn.

Что важно понимать:

  • Замыкание хранит ссылку на лексическое окружение.
  • Это может приводить к утечкам в long-living приложениях (SPA, Node.js сервисы).
  • Особенно опасно в обработчиках событий.

Практика: освобождать ссылки, аккуратно работать с глобальными кэшами, не хранить лишнее в замыканиях.


2. this — зависит от способа вызова, а не от места объявления

В JS this определяется во время вызова.

const user = {
  name: "Dmitriy",
  greet() {
    console.log(this.name);
  }
};

const greet = user.greet;
greet(); // undefined

Контекст потерян.

Правило:

  • Обычная функция → this зависит от вызова.
  • Стрелочная функция → this берётся из внешнего окружения.
  • bind, call, apply управляют контекстом вручную.

В React-классах, обработчиках DOM, старом Node-коде — это классический источник багов.


3. Event Loop и microtasks — порядок важнее, чем кажется

Большинство разработчиков знают, что JS однопоточный. Но не все понимают порядок выполнения.

console.log("1");

setTimeout(() => console.log("2"));

Promise.resolve().then(() => console.log("3"));

console.log("4");

Результат:

1
4
3
2

Почему?

  • Сначала синхронный код.
  • Потом microtasks (Promise).
  • Потом macrotasks (setTimeout).

Где это ломает код:

  • гонки состояния в UI,
  • неожиданные последовательности setState,
  • сложные async-цепочки.

Если не понимаете очереди задач — дебажить будет больно.


4. Прототипы — это реальный механизм, а не теория

class в JS — синтаксический сахар.

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hi, ${this.name}`;
  }
}

Под капотом:

User.prototype.greet = function() { ... }

Почему это важно:

  • Методы хранятся в прототипе, а не копируются в каждый объект.
  • Переопределение прототипа меняет поведение всех экземпляров.
  • Манипуляции с __proto__ могут ломать оптимизации движка.

В производительности это имеет значение.


5. Неявное приведение типов — источник «магии»

JS агрессивно приводит типы.

[] + []        // ""
[] + {}        // "[object Object]"
{} + []        // 0 (в зависимости от контекста)

Самый известный пример:

[] == false    // true

Алгоритм сравнения == сложный и неинтуитивный.

Практическое правило:

  • Использовать ===
  • Явно приводить типы
  • Не полагаться на «автоматику» языка

Вывод

JavaScript не сложный. Он неочевидный.

Если вы:

  • понимаете лексическое окружение,
  • контролируете this,
  • знаете, как работает Event Loop,
  • осознаёте прототипную модель,
  • избегаете неявных преобразований —

ваш код становится предсказуемым.

А предсказуемость — это и есть профессиональная разработка.