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

Как скрыть данные лежащие в кэше при помощи замыкания?

2.0 Middle🔥 141 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Как скрыть данные в кэше при помощи замыкания

Замыкания (closures) - это один из самых мощных инструментов в JavaScript для создания приватных данных. Это механизм, при котором функция имеет доступ к переменным из внешней области видимости, но эти переменные остаются скрытыми от прямого доступа.

1. Основной принцип замыкания

Замыкание создается когда функция обращается к переменным из своего лексического контекста:

// Простое замыкание с приватным кэшем
function createCache() {
  const cache = {};  // Приватное хранилище - скрыто от внешнего доступа
  
  return {
    // Публичный метод для получения значения
    get(key) {
      return cache[key];
    },
    
    // Публичный метод для установки значения
    set(key, value) {
      cache[key] = value;
    },
    
    // Публичный метод для проверки наличия
    has(key) {
      return key in cache;
    },
    
    // Публичный метод для удаления
    remove(key) {
      delete cache[key];
    }
  };
}

// Использование
const myCache = createCache();
myCache.set("user", { name: "John" });
console.log(myCache.get("user")); // { name: "John" }

// cache переменная скрыта, прямого доступа нет
console.log(myCache.cache); // undefined - не доступна!

Это основная идея: переменная cache лежит в замыкании функции и доступна только через публичные методы.

2. Практический пример с кэшированием функций

function memoize(fn) {
  const cache = new Map();  // Приватный кэш
  
  return function memoized(...args) {
    const key = JSON.stringify(args);  // Ключ - аргументы функции
    
    // Проверяем кэш
    if (cache.has(key)) {
      console.log("Из кэша:", args);
      return cache.get(key);
    }
    
    // Вычисляем результат
    const result = fn.apply(this, args);
    cache.set(key, result);
    
    console.log("Вычислено:", args);
    return result;
  };
}

// Использование
const expensive = memoize((a, b) => {
  // Долгое вычисление
  return a + b;
});

console.log(expensive(2, 3)); // Вычислено: [2, 3] -> 5
console.log(expensive(2, 3)); // Из кэша: [2, 3] -> 5

// Кэш скрыт, доступа нет
console.log(expensive.cache); // undefined!

3. Кэш с TTL (Time To Live)

Кэш с автоматическим истечением:

function createCacheWithTTL(ttl = 5000) {
  const cache = new Map();
  
  return {
    set(key, value) {
      cache.set(key, {
        value,
        timestamp: Date.now()
      });
    },
    
    get(key) {
      if (!cache.has(key)) {
        return undefined;
      }
      
      const { value, timestamp } = cache.get(key);
      const elapsed = Date.now() - timestamp;
      
      // Проверяем TTL
      if (elapsed > ttl) {
        cache.delete(key);
        return undefined;
      }
      
      return value;
    },
    
    clear() {
      cache.clear();
    }
  };
}

// Использование
const cache = createCacheWithTTL(3000);  // TTL 3 секунды

cache.set("api-response", { data: "sensitive" });
console.log(cache.get("api-response")); // { data: "sensitive" }

setTimeout(() => {
  console.log(cache.get("api-response")); // undefined - истекло
}, 4000);

4. Кэш для асинхронных операций

function createAsyncCache() {
  const cache = new Map();
  const pending = new Map();
  
  return async function cached(key, asyncFn) {
    // Если результат в кэше - вернуть его
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    // Если запрос уже выполняется - дождаться его
    if (pending.has(key)) {
      return pending.get(key);
    }
    
    // Запустить асинхронную функцию
    const promise = asyncFn()
      .then(result => {
        cache.set(key, result);
        pending.delete(key);
        return result;
      })
      .catch(error => {
        pending.delete(key);
        throw error;
      });
    
    pending.set(key, promise);
    return promise;
  };
}

// Использование
const apiCache = createAsyncCache();

async function fetchUser(userId) {
  return apiCache(
    `user-${userId}`,
    () => fetch(`/api/users/${userId}`).then(r => r.json())
  );
}

// Первый вызов - запрос на сервер
const user1 = await fetchUser(1);

// Второй вызов - из кэша
const user2 = await fetchUser(1);

// cache переменная скрыта, нет доступа к приватным данным

5. Защита данных от утечек

function createSecureCache(password) {
  const cache = new Map();
  
  return {
    set(key, value) {
      cache.set(key, value);
    },
    
    get(key) {
      return cache.get(key);
    },
    
    // Безопасное очищение
    clear(pwd) {
      if (pwd === password) {
        cache.clear();
        return true;
      }
      console.warn("Неправильный пароль");
      return false;
    },
    
    // Получить размер кэша (полезная инфо без доступа к данным)
    size() {
      return cache.size;
    }
  };
}

// Использование
const secure = createSecureCache("my-secret-pwd");
secure.set("credit-card", "1234-5678-9012-3456");

// Нельзя просто очистить
secure.clear("wrong-pwd"); // false

// С правильным паролем
secure.clear("my-secret-pwd"); // true

6. Паттерн Module (объединение замыканий)

const StorageManager = (() => {
  const localData = new Map();      // Приватное хранилище
  const sessionData = new Map();    // Приватное хранилище
  
  return {
    // Публичный интерфейс
    saveLocal(key, value) {
      localData.set(key, value);
    },
    
    getLocal(key) {
      return localData.get(key);
    },
    
    saveSession(key, value) {
      sessionData.set(key, value);
    },
    
    getSession(key) {
      return sessionData.get(key);
    },
    
    getStats() {
      return {
        localSize: localData.size,
        sessionSize: sessionData.size
      };
    }
  };
})();

// Использование
StorageManager.saveLocal("user", { name: "John" });
StorageManager.saveSession("token", "abc123");

console.log(StorageManager.getStats()); // { localSize: 1, sessionSize: 1 }

// Прямого доступа к localData и sessionData нет
console.log(StorageManager.localData); // undefined

7. Кэш с инвалидацией

function createInvalidatableCache() {
  const cache = new Map();
  const dependents = new Map();  // Отслеживание зависимостей
  
  return {
    set(key, value, dependencies = []) {
      cache.set(key, value);
      
      // Регистрируем зависимости
      dependencies.forEach(dep => {
        if (!dependents.has(dep)) {
          dependents.set(dep, new Set());
        }
        dependents.get(dep).add(key);
      });
    },
    
    get(key) {
      return cache.get(key);
    },
    
    invalidate(key) {
      cache.delete(key);
      
      // Инвалидировать зависимые ключи
      if (dependents.has(key)) {
        dependents.get(key).forEach(dependent => {
          cache.delete(dependent);
        });
      }
    }
  };
}

// Использование
const depCache = createInvalidatableCache();

depCache.set("user-1", { name: "John" });
depCache.set("user-1-posts", ["post1", "post2"], ["user-1"]);

console.log(depCache.get("user-1-posts")); // ["post1", "post2"]

// Инвалидируем юзера - автоматически инвалидируются посты
depCache.invalidate("user-1");
console.log(depCache.get("user-1-posts")); // undefined

8. Практический пример в React

// Кэш для API запросов
const createQueryCache = () => {
  const cache = new Map();
  
  return {
    async fetch(url, options = {}) {
      const key = `${url}:${JSON.stringify(options)}`;
      
      if (cache.has(key)) {
        return cache.get(key);
      }
      
      const response = await fetch(url, options);
      const data = await response.json();
      
      cache.set(key, data);
      return data;
    }
  };
};

// В компоненте
const queryCache = createQueryCache();

export function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    queryCache.fetch(`/api/users/${userId}`)
      .then(data => setUser(data));
  }, [userId]);
  
  if (!user) return <Skeleton />;
  return <div>{user.name}</div>;
}

Преимущества использования замыканий для кэша

  1. Инкапсуляция - данные полностью скрыты
  2. Безопасность - нельзя случайно перезаписать кэш
  3. Контроль - весь доступ идет через методы
  4. Чистота - нет загрязнения глобального пространства
  5. Предсказуемость - все операции с кэшем явные

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

Как скрыть данные лежащие в кэше при помощи замыкания? | PrepBro