← Назад к вопросам

Кешируется ли модуль при подключении с помощью require

2.0 Middle🔥 161 комментариев
#Node.js и JavaScript#Кэширование и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

require() кэширование в Node.js

Да, модули кэшируются! Это критично важная особенность Node.js, которая влияет на производительность и поведение приложения.

Как работает кэширование require

Первый импорт — исполнение кода:

// config.js
console.log('Файл config.js загружен');
module.exports = { apiUrl: 'http://localhost:3000' };

// app.js
const config = require('./config');  // Выведет: "Файл config.js загружен"
const config2 = require('./config'); // НЕ выведет ничего (использован кэш)

console.log(config === config2);     // true — один и тот же объект!

Механизм кэширования:

Node.js хранит загруженные модули в объекте require.cache:

// Смотрим кэш
console.log(require.cache);
// {
//   '/absolute/path/to/config.js': Module {
//     exports: { apiUrl: 'http://localhost:3000' }
//   },
//   ...
// }

Практический пример

// counter.js
let count = 0;
module.exports = {
  increment: () => ++count,
  getCount: () => count
};

// main.js
const counter1 = require('./counter');
const counter2 = require('./counter');

counter1.increment();
counter1.increment();

console.log(counter1.getCount()); // 2
console.log(counter2.getCount()); // 2 — ОДИН И ТОТ ЖЕ объект
console.log(counter1 === counter2); // true

Это значит, что состояние разделяется между всеми частями приложения.

Очистка кэша require.cache

Иногда нужно очистить кэш (например в тестах):

// Удалить конкретный модуль
delete require.cache[require.resolve('./config')];

// Теперь следующий require выполнит файл заново
const config = require('./config'); // Выполнится снова!

Пример: сброс кэша для конфига:

// config.js
module.exports = {
  debug: process.env.DEBUG === 'true',
  apiUrl: process.env.API_URL
};

// test.js
describe('Config', () => {
  beforeEach(() => {
    // Очищаем кэш перед каждым тестом
    delete require.cache[require.resolve('./config')];
  });
  
  it('uses DEBUG env variable', () => {
    process.env.DEBUG = 'true';
    const config = require('./config');
    expect(config.debug).toBe(true);
  });
});

Кэширование по абсолютному пути

Важно: кэширование работает по абсолютному пути!

// Если ты требуешь с разными путями — разные кэши
const mod1 = require('./module');
const mod2 = require('./module.js');
const mod3 = require('/absolute/path/module.js');

// На одних системах — один объект, на других разные!
// (зависит от решения path resolution)

Правильно:

// Всегда используй одинаковые пути
const config = require('./config'); // Одна версия в кэше
const config2 = require('./config'); // Используется кэш

Циклические зависимости

Кэширование помогает избежать бесконечных циклов:

// module-a.js
const b = require('./module-b');
module.exports = { name: 'A', b };

// module-b.js
const a = require('./module-a');
module.exports = { name: 'B', a };

// main.js
const a = require('./module-a');
// Работает благодаря кэшированию — избегаем бесконечного цикла

Как это работает:

  1. Загружаем module-a
  2. module-a требует module-b
  3. module-b требует module-a (уже в процессе загрузки)
  4. Node.js возвращает ЧАСТИЧНО загруженный module-a из кэша
  5. Оба модуля завершают загрузку

Кэширование и Performance

Плюсы кэширования:

// Быстрое потребление памяти — модуль загружается один раз
const users = require('./models/users');
const orders = require('./models/orders');
const payments = require('./models/payments');
// Все используют один instance базы данных

Минусы — общее состояние может быть проблемой:

// database.js
const connections = [];
module.exports = {
  addConnection: (conn) => connections.push(conn),
  getConnections: () => connections
};

// service1.js
const db = require('./database');
db.addConnection({ id: 1 });

// service2.js
const db = require('./database');
console.log(db.getConnections()); // Видит соединение из service1!

Это может быть как плюсом (разделение ресурсов), так и минусом (побочные эффекты).

CommonJS vs ES6 modules

CommonJS (require) — кэшируется:

const config = require('./config');
const config2 = require('./config');
console.log(config === config2); // true

ES6 import — тоже кэшируется (в Node.js 12+):

import config from './config.js';
import config2 from './config.js';
console.log(config === config2); // true

Отключение кэша (редко нужно)

// Создание нового instance каждый раз
function requireFresh(modulePath) {
  delete require.cache[require.resolve(modulePath)];
  return require(modulePath);
}

// Использование
const config1 = requireFresh('./config');
const config2 = requireFresh('./config');
console.log(config1 === config2); // false — разные объекты

Когда нужно отключение кэша:

  • Тестирование с разными конфигами
  • Динамическая перезагрузка модулей (редко)
  • HMR (Hot Module Replacement) в dev tools

Практический пример: Singleton паттерн

Кэширование require идеально для Singleton:

// database.js
class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    this.pool = null; // Инициализация
    Database.instance = this;
  }
  
  connect(url) {
    this.pool = new Pool({ connectionString: url });
  }
  
  getPool() {
    return this.pool;
  }
}

module.exports = new Database();

// app.js
const db = require('./database');
const db2 = require('./database');

console.log(db === db2); // true — один instance
db.connect('postgres://...');
// db2 видит то же соединение

Изучение кэша

// Функция для отладки кэша
function showCache(pattern) {
  Object.keys(require.cache)
    .filter(key => key.includes(pattern))
    .forEach(key => {
      console.log(key);
    });
}

showCache('config');  // Показывает все загруженные config модули
showCache('database'); // Показывает все database модули

Итого: ключевые моменты

  1. require() кэшируется автоматически по абсолютному пути модуля
  2. Второй и все последующие требования используют кэш — код модуля не выполняется снова
  3. Вернётся одного объект — require.cache содержит тот же экземпляр
  4. Состояние разделяется — все части кода видят одно и то же состояние модуля
  5. Очистка кэша: delete require.cache[require.resolve('./module')]
  6. Цикличеч зависимости работают благодаря кэшированию
  7. Кэширование помогает реализовать Singleton паттерн

Это одна из самых важных особенностей Node.js, которая влияет на архитектуру приложения.