Мемоизация (memoization) — это техника оптимизации, которая заключается в кэшировании результатов выполнения функций для одних и тех же входных параметров, чтобы избежать повторных вычислений.
Простейший вариант с 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 (из кэша)
Для методов класса:
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;
}
}
Проблема: методы в шаблонах вызываются при каждом цикле изменений
Решение 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;
}
}
import memoize from 'lodash.memoize';
const memoizedFunc = memoize((param) => {
// сложные вычисления
});
import { createSelector } from '@ngrx/store';
const selectItems = (state: AppState) => state.items;
export const selectFilteredItems = createSelector(
selectItems,
(items) => items.filter(item => item.active)
);
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 мемоизация особенно полезна для оптимизации работы шаблонов и селекторов состояния.