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

Как использовал декораторы методов?

1.0 Junior🔥 201 комментариев
#JavaScript Core

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

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

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

Декораторы методов в JavaScript/TypeScript

Что такое декоратор

Декоратор — это функция, которая принимает другую функцию и расширяет её поведение без изменения исходного кода. Это паттерн из функционального программирования, который позволяет добавлять функциональность к существующим методам.

1. Простой декоратор (обычная функция)

// Базовый декоратор для логирования
function logDecorator(fn) {
  return function(...args) {
    console.log(`Вызов функции с аргументами: ${args}`);
    const result = fn.apply(this, args);
    console.log(`Результат: ${result}`);
    return result;
  };
}

function add(a, b) {
  return a + b;
}

const addWithLog = logDecorator(add);
addWithLog(5, 3);
// Вывод:
// Вызов функции с аргументами: 5,3
// Результат: 8

2. Декоратор для методов класса (TypeScript)

В TypeScript декораторы — это встроенная функция (необходимо включить в tsconfig.json: "experimentalDecorators": true):

// Декоратор для логирования вызовов метода
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Метод ${propertyKey} вызван с аргументами:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Результат:`, result);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(5, 3);
// Вывод:
// Метод add вызван с аргументами: [5, 3]
// Результат: 8

3. Декоратор для кэширования результатов

function Memoize(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const cache = new Map();
  
  descriptor.value = function(...args: any[]) {
    const cacheKey = JSON.stringify(args);
    
    if (cache.has(cacheKey)) {
      console.log(`Возвращен из кэша: ${cacheKey}`);
      return cache.get(cacheKey);
    }
    
    console.log(`Вычисление для: ${cacheKey}`);
    const result = originalMethod.apply(this, args);
    cache.set(cacheKey, result);
    return result;
  };
  
  return descriptor;
}

class Fibonacci {
  @Memoize
  calculate(n: number): number {
    if (n <= 1) return n;
    return this.calculate(n - 1) + this.calculate(n - 2);
  }
}

const fib = new Fibonacci();
console.time('First call');
fib.calculate(10); // Долгое вычисление
console.timeEnd('First call');

console.time('Second call');
fib.calculate(10); // Из кэша
console.timeEnd('Second call');

4. Декоратор для перехвата ошибок

function HandleError(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = async function(...args: any[]) {
    try {
      return await originalMethod.apply(this, args);
    } catch (error) {
      console.error(`Ошибка в методе ${propertyKey}:`, error);
      // Логирование, отправка на сервер, и т.д.
      return null;
    }
  };
  
  return descriptor;
}

class UserService {
  @HandleError
  async fetchUser(id: number) {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error('User not found');
    return response.json();
  }
}

const userService = new UserService();
await userService.fetchUser(999); // Ошибка будет перехвачена

5. Декоратор для проверки прав доступа

function RequireRole(role: string) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
      const currentUser = getCurrentUser(); // Твоя функция
      
      if (!currentUser || !currentUser.roles.includes(role)) {
        throw new Error(`Доступ запрещен. Требуется роль: ${role}`);
      }
      
      return originalMethod.apply(this, args);
    };
    
    return descriptor;
  };
}

class AdminPanel {
  @RequireRole('admin')
  deleteUser(userId: number): void {
    console.log(`Пользователь ${userId} удален`);
  }
}

const panel = new AdminPanel();
panel.deleteUser(123); // Выбросит ошибку, если нет роли admin

6. Декоратор для валидации аргументов

function ValidateArgs(...validators: Array<(arg: any) => boolean>) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
      for (let i = 0; i < validators.length; i++) {
        if (!validators[i](args[i])) {
          throw new Error(`Ошибка валидации аргумента ${i}`);
        }
      }
      
      return originalMethod.apply(this, args);
    };
    
    return descriptor;
  };
}

class UserValidator {
  @ValidateArgs(
    (email) => typeof email === 'string' && email.includes('@'),
    (age) => typeof age === 'number' && age >= 18
  )
  createUser(email: string, age: number) {
    console.log(`Создан пользователь: ${email}, возраст: ${age}`);
  }
}

const validator = new UserValidator();
validator.createUser('john@example.com', 25); // OK
validator.createUser('invalid-email', 25); // Ошибка валидации

7. Декоратор для ограничения частоты вызовов (Debounce/Throttle)

function Debounce(delay: number) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    let timeoutId: NodeJS.Timeout | null = null;
    
    descriptor.value = function(...args: any[]) {
      if (timeoutId) clearTimeout(timeoutId);
      
      timeoutId = setTimeout(() => {
        originalMethod.apply(this, args);
      }, delay);
    };
    
    return descriptor;
  };
}

function Throttle(delay: number) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    let lastCall = 0;
    
    descriptor.value = function(...args: any[]) {
      const now = Date.now();
      
      if (now - lastCall >= delay) {
        lastCall = now;
        originalMethod.apply(this, args);
      }
    };
    
    return descriptor;
  };
}

class SearchComponent {
  @Debounce(500)
  handleSearch(query: string) {
    console.log(`Поиск по запросу: ${query}`);
    // API запрос
  }
  
  @Throttle(1000)
  handleScroll() {
    console.log('Scroll event');
  }
}

8. Комбинирование нескольких декораторов

class OrderService {
  @Log
  @HandleError
  @RequireRole('admin')
  async cancelOrder(orderId: number) {
    // Логирование, обработка ошибок, проверка прав
    const response = await fetch(`/api/orders/${orderId}`, {
      method: 'DELETE'
    });
    return response.json();
  }
}

// Порядок выполнения: Log -> HandleError -> RequireRole -> метод

9. Декоратор для параметров класса

function AutoBind(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  return {
    get() {
      return originalMethod.bind(this);
    },
    set() {
      // Запретить переназначение
      throw new Error('Cannot reassign method');
    },
    enumerable: false,
    configurable: true
  };
}

class Button {
  label = 'Click me';
  
  @AutoBind
  onClick() {
    console.log(`Button clicked: ${this.label}`);
  }
}

const button = new Button();
const handler = button.onClick;
handler(); // 'this' всегда привязан к button

10. Декоратор для отложенного выполнения

function Async(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    return Promise.resolve(originalMethod.apply(this, args));
  };
  
  return descriptor;
}

class DataLoader {
  @Async
  loadData() {
    return { id: 1, name: 'John' };
  }
}

const loader = new DataLoader();
await loader.loadData(); // Результат обернут в Promise

Практические примеры использования

// В React компонентах через класс
class UserProfile {
  @Memoize
  @Log
  getUserInfo(userId: number) {
    // Кэширует результат и логирует вызовы
  }
  
  @HandleError
  @RequireRole('user')
  async updateProfile(data: UserData) {
    // Обрабатывает ошибки и проверяет права
  }
}

Итоги

Декораторы используются для:

  • Логирования и отладки
  • Кэширования результатов
  • Валидации аргументов
  • Управления ошибками
  • Проверки прав доступа
  • Ограничения частоты вызовов
  • Привязки контекста

Поддержка:

  • TypeScript: встроенная поддержка (флаг experimentalDecorators)
  • JavaScript: нужны babel плагины для stage-3 декораторов
  • Практически все современные фреймворки (Angular, NestJS, class-validators) используют декораторы
Как использовал декораторы методов? | PrepBro