Комментарии (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) используют декораторы