Глобальные объекты Node.js: globalThis, Buffer, таймеры

✓ Все примеры выполнены на Node.js v22.23.1 (LTS), июль 2026

Содержание

В Node нет window, но есть свой набор объектов, доступных в любом файле без единого импорта. За последние годы этот набор сильно вырос: половина того, ради чего раньше ставили пакеты, теперь встроена в платформу.

globalThis вместо global

globalThis — стандартное имя глобального объекта, работающее везде. В Node оно указывает на тот же объект, что и старое global:

console.log('globalThis === global :', globalThis === global);
console.log('есть ли window        :', typeof window);
globalThis === global : true
есть ли window        : undefined

Разница только в переносимости: global понимает лишь Node, window — только браузер, а globalThis — обе среды.

Отдельно скажем очевидное: писать свои переменные в глобальный объект не нужно. Это выглядит удобно («доступ отовсюду»), но превращает модули в неявно связанные и ломает тесты. Нужен общий объект — экспортируйте его из модуля.

Что уже есть без импорта

for (const g of ['Buffer', 'URL', 'fetch', 'AbortController', 'structuredClone',
                 'queueMicrotask', 'setTimeout', 'setImmediate', 'console', 'TextEncoder']) {
  console.log(`  ${g.padEnd(16)}: ${typeof globalThis[g]}`);
}
  Buffer          : function
  URL             : function
  fetch           : function
  AbortController : function
  structuredClone : function
  queueMicrotask  : function
  setTimeout      : function
  setImmediate    : function
  console         : object
  TextEncoder     : function
Объект Зачем Что заменил
fetch HTTP-запросы node-fetch, axios, request
AbortController отмена операций по сигналу самописные флаги отмены
structuredClone глубокая копия lodash.cloneDeep
URL разбор и сборка адресов устаревший url.parse()
Buffer бинарные данные — (специфика Node)
TextEncoder строка ↔ байты ручная возня с Buffer

Практический вывод: прежде чем ставить зависимость ради мелкой утилиты, проверьте — возможно, она уже в платформе.

structuredClone: копия, которая не ломает Date

Классический трюк «глубокой копии» через JSON тихо портит данные: Date превращается в строку, Map и Set — в пустой объект, undefined исчезает.

const src = { d: new Date('2026-07-16'), m: new Map([['a', 1]]), nested: { x: [1, 2] } };
const clone = structuredClone(src);
clone.nested.x.push(3);

console.log('оригинал не изменился  :', src.nested.x);
console.log('Date остался Date      :', clone.d instanceof Date);
console.log('Map остался Map        :', clone.m instanceof Map);
console.log('через JSON Date стал бы:', typeof JSON.parse(JSON.stringify(src)).d);
оригинал не изменился  : [ 1, 2 ]
Date остался Date      : true
Map остался Map        : true
через JSON Date стал бы: string

Ограничение у structuredClone одно: функции и классы он не копирует — бросит DataCloneError. Для данных этого достаточно.

Порядок выполнения: замер, а не теория

Здесь живёт самое частое заблуждение. Запланируем всё сразу и посмотрим на фактический порядок.

setTimeout(() => order.push('setTimeout(0)'), 0);
setImmediate(() => order.push('setImmediate'));
Promise.resolve().then(() => order.push('промис'));
queueMicrotask(() => order.push('queueMicrotask'));
process.nextTick(() => order.push('process.nextTick'));
order.push('синхронный код');

Тот же код, запущенный в двух разных контекстах, даёт разный порядок:

чистый top-level      : синхронный код -> process.nextTick -> промис -> queueMicrotask -> setImmediate -> setTimeout(0)
после top-level await : синхронный код -> промис -> queueMicrotask -> process.nextTick -> setImmediate -> setTimeout(0)

Первая строка — тот самый канонический порядок из учебников: nextTick идёт раньше промисов.

Вторая строка — то, о чём учебники молчат. Если код выполняется уже внутри промис-континуации (а именно так работает всё, что идёт после await), новые микрозадачи попадают в очередь, которая разбирается прямо сейчас, и успевают вперёд nextTick.

Практический вывод: не стройте логику на приоритете nextTick перед промисами. Он зависит от того, откуда вызван ваш код, а это меняется при любом рефакторинге, добавляющем await выше по стеку.

Механизм Когда выполнится Для чего
process.nextTick сразу после текущей операции внутренние нужды Node; в прикладном коде почти не нужен
queueMicrotask в очереди микрозадач отложить на «сразу после текущего кода»
Promise.then там же, где микрозадачи обычная асинхронность
setImmediate в фазе check событийного цикла «после ввода-вывода»
setTimeout(0) в фазе таймеров «когда-нибудь скоро», не раньше

setTimeout(fn, 0) не значит «немедленно»: ноль — это минимальная задержка, а не гарантия. Если нужно именно «после текущей операции», берите queueMicrotask или setImmediate.

Порядок выполнения на чистом верхнем уровне
  1. 1 Синхронный кодВыполняется весь, до последней строки
  2. 2 process.nextTickРаньше промисов — но только в этом контексте
  3. 3 МикрозадачиPromise.then, queueMicrotask — в порядке постановки
  4. 4 setImmediateФаза check событийного цикла
  5. 5 setTimeout(…, 0)Фаза таймеров. Ноль — минимум задержки, не гарантия

Таймеры возвращают объект, а не число

Отличие от браузера, на которое натыкаются: setTimeout в Node возвращает объект Timeout, а не числовой id.

const t = setTimeout(() => {}, 1000);
t.unref();   // не держать процесс живым ради этого таймера
clearTimeout(t);

Метод unref() — полезная специфика Node: таймер продолжит работать, но перестанет удерживать процесс от завершения. Классическая причина «скрипт отработал, а процесс не выходит» — как раз забытый живой интервал. Второй по частоте виновник — незакрытый HTTP-сервер.

Промис-версии таймеров живут в отдельном модуле и часто удобнее:

import { setTimeout as sleep } from 'node:timers/promises';
await sleep(1000);

Что было на этой странице в 2018 году

Первая версия вышла 20 апреля 2018 года и начиналась так:

«Два важнейших объекта в Node — global и process

Утверждение верное — настолько, что за прошедшие годы process дорос до размера, при котором разбирать его в одной статье с глобальными объектами стало неудобно. Поэтому здесь мы говорим про контейнер, а process вынесен в отдельный разбор.

Главное объяснение той версии не устарело ни на слово:

«В браузере переменная, объявленная на верхнем уровне, объявляется глобально. В Node дело обстоит иначе. Переменная, объявленная в модуле, не обладает глобальной доступностью; она ограничивается модулем.»

Это по-прежнему первое, что нужно понять про Node: модуль — не глобальная область. Объявили const base = 2 в двух разных файлах — конфликта не будет, это две разные переменные. В браузере 2018 года два скрипта с одинаковым var затирали друг друга, и разница действительно требовала объяснения.

Что изменилось с той версии: примеры были на var и require, globalThis в статье не упоминался (он появился в Node 12, годом позже), а fetch, structuredClone и AbortController глобально ещё не существовали — за каждым ходили в npm. Собственно, весь раздел «Что уже есть без импорта» выше — это список того, чего в 2018-м на этой странице просто не могло быть.

Buffer: почему не обычный массив

Buffer — самый «нодовый» из глобальных объектов: в браузере его нет. Он появился раньше, чем в JavaScript завезли типизированные массивы, и остался как основной способ работы с байтами.

const buf = Buffer.from('привет', 'utf8');
console.log(buf.length);          // 12 — байты, а не символы
console.log(buf.toString('hex')); // d0bfd180d0b8d0b2d0b5d182

Формально Buffer — наследник Uint8Array, поэтому всё, что принимает типизированный массив, примет и его. Обратное неверно: методы вроде toString('base64') есть только у Buffer.

Одна ловушка, о которой стоит знать: Buffer.allocUnsafe(n) быстрее Buffer.alloc(n), потому что не обнуляет память — в выделенном куске лежат остатки предыдущих данных процесса. Если такой буфер отдать наружу не заполнив целиком, вы отправите клиенту случайные фрагменты чужой памяти. Пока не упрётесь в производительность, берите Buffer.alloc.

Про __dirname

В ESM-модулях __dirname и __filename не существуют — они были частью CommonJS. Замена штатная: import.meta.dirname и import.meta.filename (доступны с Node 21.2). Подробнее — в статье про модуль fs.


Смежные темы: объект process — самый большой из глобальных; промисы — где пригодится AbortController; модуль fs — где чаще всего встречается Buffer. Полный список — в справочнике по Node.js.

Частые вопросы

`global` устарел?
Формально нет, но globalThis — стандарт и работает везде. Новый код пишите на нём.
Почему в Node нет `window` и `document`?
Это объекты браузера: окно и DOM-дерево. У серверного процесса ни того, ни другого просто нет.
`fetch` в Node — тот же самый, что в браузере?
Это реализация того же стандарта поверх undici. Отличия есть в мелочах (прокси, сертификаты), но API совпадает.
Стоит ли использовать `process.nextTick`?
В прикладном коде — почти никогда. Как показал замер, его приоритет зависит от контекста вызова. Нужна отложенность — берите queueMicrotask или setImmediate.