Что такое мемоизация и как ее использовать?angular-74

Мемоизация (memoization) — это техника оптимизации, которая заключается в кэшировании результатов выполнения функций для одних и тех же входных параметров, чтобы избежать повторных вычислений.

Основные принципы мемоизации

  1. Кэширование результатов: функция запоминает результат для конкретных аргументов
  2. Идемпотентность: функция должна быть чистой (pure) - одинаковые входные данные всегда дают одинаковый результат
  3. Автоматическая инвалидация: обычно кэш очищается при изменении аргументов

Способы реализации мемоизации в Angular

1. Ручная реализация

Простейший вариант с Map для хранения результатов:

function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();

  return ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn(...args);
    cache.set(key, result);
    return result;
  }) as T;
}

Пример использования:

const expensiveCalculation = memoize((n: number) => {
  console.log('Вычисляю...');
  return n * n;
});

console.log(expensiveCalculation(5)); // Вычисляю... 25
console.log(expensiveCalculation(5)); // 25 (из кэша)

2. Использование декораторов

Для методов класса:

function Memoize() {
  const cache = new Map<string, any>();

  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      const key = JSON.stringify(args);

      if (cache.has(key)) {
        return cache.get(key);
      }

      const result = originalMethod.apply(this, args);
      cache.set(key, result);
      return result;
    };

    return descriptor;
  };
}

Использование:

class Calculator {
  @Memoize()
  compute(n: number): number {
    console.log('Вычисляю...');
    return n * n;
  }
}

3. Мемоизация в Angular-шаблонах

Проблема: методы в шаблонах вызываются при каждом цикле изменений

Решение 1: Использование pipes

@Pipe({ name: 'memoize' })
export class MemoizePipe implements PipeTransform {
  private cache = new Map<any, any>();

  transform(value: any, fn: (arg: any) => any): any {
    if (!this.cache.has(value)) {
      this.cache.set(value, fn(value));
    }
    return this.cache.get(value);
  }
}

Использование в шаблоне:

<div>{{ value | memoize: expensiveFunction }}</div>

Решение 2: Использование getter с кэшированием

@Component({...})
export class MyComponent {
  private _result: any;
  private _lastInput: any;

  get memoizedResult() {
    if (this.input !== this._lastInput) {
      this._lastInput = this.input;
      this._result = this.expensiveCalculation(this.input);
    }
    return this._result;
  }
}

4. Библиотеки для мемоизации

  1. lodash.memoize
    Простая реализация мемоизации:
import memoize from 'lodash.memoize';

const memoizedFunc = memoize((param) => {
  // сложные вычисления
});
  1. reselect
    Для мемоизации селекторов в NgRx:
import { createSelector } from '@ngrx/store';

const selectItems = (state: AppState) => state.items;

export const selectFilteredItems = createSelector(
  selectItems,
  (items) => items.filter(item => item.active)
);

Когда использовать мемоизацию

  1. Тяжелые вычисления в компонентах или сервисах
  2. Часто вызываемые функции с одинаковыми аргументами
  3. Селекторы в NgRx/State management
  4. Оптимизация рендеринга (для предотвращения лишних вычислений в шаблонах)

Когда не использовать мемоизацию

  1. Простые функции (оверхед кэширования может быть больше выгоды)
  2. Функции с побочными эффектами
  3. Когда аргументы часто меняются (кэш будет бесполезен)
  4. Для функций с большим количеством уникальных аргументов (риск утечки памяти)

Продвинутые техники

Мемоизация с TTL

function memoizeWithTTL(fn: Function, ttl: number = 1000) {
  const cache = new Map<string, { value: any, expires: number }>();

  return (...args: any[]) => {
    const key = JSON.stringify(args);
    const now = Date.now();

    if (cache.has(key) {
      const entry = cache.get(key);
      if (entry.expires > now) {
        return entry.value;
      }
    }

    const result = fn(...args);
    cache.set(key, { value: result, expires: now + ttl });
    return result;
  };
}

Мемоизация асинхронных функций

function asyncMemoize<T extends (...args: any[]) => Promise<any>>(fn: T): T {
  const cache = new Map<string, Promise<ReturnType<T>>>();

  return ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn(...args);
    cache.set(key, result);
    return result;
  }) as T;
}

Резюмируем

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