Как JavaScript обрабатывает код? Основные механизмы и примеры

Раздел: Изучение JavaScript -> Введение в JavaScript

Как работает JavaScript

Как браузер выполняет JavaScript код?

Основной механизм: парсинг, компиляция и выполнение

Когда браузер встречает тег script, движок JavaScript (например, V8) начинает работу. Сначала происходит лексический анализ, разбивающий код на токены. Затем строится абстрактное синтаксическое дерево (AST), которое преобразуется в байткод. Современные движки используют JIT (Just-In-Time) компиляцию: часто выполняемые участки кода компилируются в машинный код для ускорения.


// Пример простого кода
console.log('Привет, мир!');
  

как работает js (как работает javascript)

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

Типичная ошибка:

Ошибка ReferenceError при обращении к необъявленной переменной: console.log(x); // x не определена. Решение: объявить переменную с let, const или var.

Какие альтернативные способы выполнения существуют?

Раньше JavaScript был только интерпретируемым, без JIT. Некоторые среды (например, старые браузеры) просто интерпретируют байткод. В среде Node.js также используется V8 с JIT. Также существуют компиляторы в байткод других платформ (например, для мобильных приложений через React Native).

Проблема:

При использовании eval или new Function код компилируется на лету, что может замедлить выполнение и создать риск безопасности.

Как JavaScript обрабатывает асинхронные задачи?

Событийный цикл (event loop) и очередь задач

JavaScript однопоточен, но не блокирует выполнение благодаря асинхронной модели. Стек вызовов выполняет синхронный код, а асинхронные операции (setTimeout, fetch, события) помещаются в очередь задач. Когда стек очищается, цикл событий берет следующую задачу и выполняет ее.


console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// Вывод: 1, 3, 2
  

что делает js (что такое javascript и что он делает)

В этом примере setTimeout с задержкой 0 помещает колбэк в очередь, поэтому "2" выводится после синхронного кода.

Ошибка:

Забыть об асинхронности и ожидать, что код выполнится последовательно. Например, при работе с асинхронным API данные могут быть не готовы сразу. Решение: использовать колбэки, промисы или async/await.

Какие инструменты для работы с асинхронностью появились в ES6+?

Промисы (Promise) позволяют избежать вложенных колбэков. Async/await (ES2017) делает асинхронный код похожим на синхронный.


async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
}
  

Этот код эквивалентен цепочке then, но более читаем.

Ошибка:

Необработанное отклонение промиса (unhandled rejection). Решение: добавлять catch или try/catch в async функциях.

Как JavaScript управляет памятью?

Стек, куча и сборка мусора

Примитивные значения (числа, строки, булевы) хранятся в стеке. Объекты, массивы, функции - в куче. Сборщик мусора автоматически освобождает память, когда объекты становятся недостижимыми. Основной алгоритм - mark-and-sweep.


let obj = { name: 'Alice' };
obj = null; // объект теперь недостижим, будет собран
  

Замыкания могут удерживать ссылки на переменные внешней функции, предотвращая сборку мусора при необходимости.

Утечка памяти:

Глобальные переменные или забытые таймеры могут удерживать ссылки. Например, setTimeout внутри функции, который ссылается на большие данные. Решение: очищать таймеры clearTimeout, избегать глобальных переменных.

Как слабые ссылки помогают управлять памятью?

WeakMap и WeakSet хранят ссылки, которые не препятствуют сборке мусора. Они удобны для кэширования данных, связанных с объектами.


let cache = new WeakMap();
function process(obj) {
  if (!cache.has(obj)) {
    cache.set(obj, computeExpensive(obj));
  }
  return cache.get(obj);
}
  

Когда объект obj удаляется, запись в WeakMap автоматически удаляется.

Ошибка:

Слабая ссылка не защищает от удаления, если объект имеет только слабые ссылки. Нельзя обходить WeakMap итерацией.

Расширенные примеры работы JavaScript

Пример 1: Порядок выполнения асинхронных операций

Показана разница между макротасками (setTimeout) и микротасками (Promise). Микротаски выполняются сразу после завершения текущей макротаски, перед следующим макротаском.

Пример

console.log('Синхронный 1');

setTimeout(() => console.log('Макротаска'), 0);

Promise.resolve().then(() => console.log('Микротаска'));

console.log('Синхронный 2');
Синхронный 1
Синхронный 2
Микротаска
Макротаска

Объяснение: сначала выполняется весь синхронный код, затем очередь микротасок (Promise), затем макротаски (setTimeout).

Пример 2: Замыкания и утечка памяти

Функция возвращает другую функцию, которая ссылается на внешний массив. Если не освободить ссылку, массив останется в памяти.

Пример

function createClosure() {
  let largeArray = new Array(1000000).fill('data');
  return function() {
    console.log(largeArray.length);
  };
}

const closure = createClosure();
// largeArray не будет собран, пока существует closure
closure();
// Для освобождения:
// closure = null;

Решение: обнулить ссылку на замыкание, когда оно больше не нужно.

Пример 3: WeakMap для приватных данных

Слабые ссылки позволяют хранить данные, привязанные к объекту, без предотвращения сборки мусора.

Пример

const privateData = new WeakMap();

class Person {
  constructor(name) {
    privateData.set(this, { name });
  }

  getName() {
    return privateData.get(this).name;
  }
}

let person = new Person('Alice');
console.log(person.getName()); // Alice
person = null; // WeakMap автоматически очищает запись

Этот подход защищает данные от внешнего доступа и не создает утечек.

Пример 4: JIT компиляция и оптимизация

Движок V8 отслеживает горячие функции (вызываемые много раз) и компилирует их в машинный код. Напишем функцию суммирования и вызовем её многократно, чтобы увидеть разницу в производительности.

Пример

function sum(a, b) {
  return a + b;
}

let start = performance.now();
for (let i = 0; i < 1000000; i++) {
  sum(i, i+1);
}
let end = performance.now();
console.log('Время выполнения: ' + (end - start) + ' мс');

На первых вызовах функция может интерпретироваться, затем после обнаружения однотипных аргументов (числа) движок оптимизирует её. Результат может варьироваться в разных средах.

Как работает JavaScript - comments

En
как работает js (javascript)