Декоратор — это специальная функция, которая добавляет поведение классу, методу или свойству без изменения их исходного кода. Декораторы включаются флагом experimentalDecorators: true в tsconfig.json.
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true // для работы с metadata
}
}Принимает конструктор класса, может его модифицировать или заменить:
function Sealed(constructor: Function) {
Object.seal(constructor) // нельзя добавить статические свойства
Object.seal(constructor.prototype) // нельзя добавить методы в прототип
}
function AddTimestamp<T extends { new(...args: any[]): {} }>(Base: T) {
return class extends Base {
createdAt = new Date()
}
}
@Sealed
@AddTimestamp
class User {
constructor(public name: string) {}
}
const u = new User('Алексей')
console.log((u as any).createdAt) // Date добавлена декораторомПринимает три аргумента: прототип, имя метода, дескриптор:
function Log(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`Вызов ${name}(${args.join(', ')})`)
const result = original.apply(this, args)
console.log(`Результат: ${result}`)
return result
}
return descriptor
}
function Memoize(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
const cache = new Map()
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args)
if (cache.has(key)) return cache.get(key)
const result = original.apply(this, args)
cache.set(key, result)
return result
}
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b
}
@Memoize
fibonacci(n: number): number {
if (n <= 1) return n
return this.fibonacci(n - 1) + this.fibonacci(n - 2)
}
}function Required(target: any, key: string) {
let value = target[key]
Object.defineProperty(target, key, {
get() { return value },
set(newVal) {
if (newVal == null || newVal === '') {
throw new Error(`Поле ${key} обязательно`)
}
value = newVal
}
})
}
class Form {
@Required
name: string = ''
@Required
email: string = ''
}Для передачи параметров декоратор оборачивают в функцию:
function Throttle(ms: number) {
return function(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
let lastCall = 0
descriptor.value = function (...args: any[]) {
const now = Date.now()
if (now - lastCall >= ms) {
lastCall = now
return original.apply(this, args)
}
}
return descriptor
}
}
class SearchInput {
@Throttle(300)
search(query: string) {
console.log('Поиск:', query)
}
}Декораторы активно используются во фреймворках:
// NestJS — веб-фреймворк
@Controller('/users')
class UserController {
@Get('/:id')
@UseGuards(AuthGuard)
async getUser(@Param('id') id: string) { ... }
}
// TypeORM — ORM для баз данных
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
email: string
}Декораторы через обычные функции-обёртки: memoize, throttle, readonly, log — те же паттерны, что и TS-декораторы
// Декораторы в TypeScript компилируются в вызовы функций.
// Покажем те же паттерны через обычные функции в JS.
// --- Декоратор метода: memoize ---
function memoize(fn) {
const cache = new Map()
return function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log(`[memoize] кэш: ${key}`)
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// --- Декоратор метода: log ---
function log(fn, name = fn.name) {
return function (...args) {
console.log(`[log] ${name}(${args.join(', ')})`)
const result = fn.apply(this, args)
console.log(`[log] ${name} → ${result}`)
return result
}
}
// --- Декоратор метода: throttle ---
function throttle(fn, ms) {
let lastCall = 0
return function (...args) {
const now = Date.now()
if (now - lastCall >= ms) {
lastCall = now
return fn.apply(this, args)
} else {
console.log(`[throttle] пропущен: прошло ${now - lastCall}мс из ${ms}мс`)
}
}
}
// --- Декоратор метода: retry ---
function retry(fn, attempts = 3) {
return async function (...args) {
for (let i = 0; i < attempts; i++) {
try {
return await fn.apply(this, args)
} catch (e) {
console.log(`[retry] попытка ${i + 1} не удалась: ${e.message}`)
if (i === attempts - 1) throw e
}
}
}
}
// --- Декоратор класса: sealed (запрет изменения прототипа) ---
function sealed(Class) {
Object.seal(Class)
Object.seal(Class.prototype)
return Class
}
// --- Применение ---
class Calculator {
fibonacci(n) {
if (n <= 1) return n
return this.fibonacci(n - 1) + this.fibonacci(n - 2)
}
add(a, b) { return a + b }
}
const calc = new Calculator()
// Применяем memoize + log вручную (как decorator)
calc.fibonacci = memoize(calc.fibonacci.bind(calc))
console.log('=== Memoize: fibonacci ===')
console.log(calc.fibonacci(10)) // вычисляет
console.log(calc.fibonacci(10)) // из кэша
const loggedAdd = log(calc.add.bind(calc), 'add')
console.log('\n=== Log: add ===')
loggedAdd(3, 5)
loggedAdd(10, 20)
console.log('\n=== Throttle: search ===')
const search = throttle((query) => {
console.log('Поиск:', query)
}, 200)
search('TypeScript') // выполнится
search('декораторы') // пропустится (< 200мс)
setTimeout(() => {
search('JavaScript') // выполнится (>= 200мс)
}, 250)
console.log('\n=== Retry: fetch-like ===')
let attempts = 0
const unreliable = retry(async () => {
attempts++
if (attempts < 3) throw new Error('Сеть недоступна')
return 'Данные получены'
}, 3)
unreliable().then(result => {
console.log('Результат:', result)
console.log('Попыток потребовалось:', attempts)
})Декоратор — это специальная функция, которая добавляет поведение классу, методу или свойству без изменения их исходного кода. Декораторы включаются флагом experimentalDecorators: true в tsconfig.json.
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true // для работы с metadata
}
}Принимает конструктор класса, может его модифицировать или заменить:
function Sealed(constructor: Function) {
Object.seal(constructor) // нельзя добавить статические свойства
Object.seal(constructor.prototype) // нельзя добавить методы в прототип
}
function AddTimestamp<T extends { new(...args: any[]): {} }>(Base: T) {
return class extends Base {
createdAt = new Date()
}
}
@Sealed
@AddTimestamp
class User {
constructor(public name: string) {}
}
const u = new User('Алексей')
console.log((u as any).createdAt) // Date добавлена декораторомПринимает три аргумента: прототип, имя метода, дескриптор:
function Log(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`Вызов ${name}(${args.join(', ')})`)
const result = original.apply(this, args)
console.log(`Результат: ${result}`)
return result
}
return descriptor
}
function Memoize(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
const cache = new Map()
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args)
if (cache.has(key)) return cache.get(key)
const result = original.apply(this, args)
cache.set(key, result)
return result
}
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b
}
@Memoize
fibonacci(n: number): number {
if (n <= 1) return n
return this.fibonacci(n - 1) + this.fibonacci(n - 2)
}
}function Required(target: any, key: string) {
let value = target[key]
Object.defineProperty(target, key, {
get() { return value },
set(newVal) {
if (newVal == null || newVal === '') {
throw new Error(`Поле ${key} обязательно`)
}
value = newVal
}
})
}
class Form {
@Required
name: string = ''
@Required
email: string = ''
}Для передачи параметров декоратор оборачивают в функцию:
function Throttle(ms: number) {
return function(target: any, name: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
let lastCall = 0
descriptor.value = function (...args: any[]) {
const now = Date.now()
if (now - lastCall >= ms) {
lastCall = now
return original.apply(this, args)
}
}
return descriptor
}
}
class SearchInput {
@Throttle(300)
search(query: string) {
console.log('Поиск:', query)
}
}Декораторы активно используются во фреймворках:
// NestJS — веб-фреймворк
@Controller('/users')
class UserController {
@Get('/:id')
@UseGuards(AuthGuard)
async getUser(@Param('id') id: string) { ... }
}
// TypeORM — ORM для баз данных
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
email: string
}Декораторы через обычные функции-обёртки: memoize, throttle, readonly, log — те же паттерны, что и TS-декораторы
// Декораторы в TypeScript компилируются в вызовы функций.
// Покажем те же паттерны через обычные функции в JS.
// --- Декоратор метода: memoize ---
function memoize(fn) {
const cache = new Map()
return function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log(`[memoize] кэш: ${key}`)
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// --- Декоратор метода: log ---
function log(fn, name = fn.name) {
return function (...args) {
console.log(`[log] ${name}(${args.join(', ')})`)
const result = fn.apply(this, args)
console.log(`[log] ${name} → ${result}`)
return result
}
}
// --- Декоратор метода: throttle ---
function throttle(fn, ms) {
let lastCall = 0
return function (...args) {
const now = Date.now()
if (now - lastCall >= ms) {
lastCall = now
return fn.apply(this, args)
} else {
console.log(`[throttle] пропущен: прошло ${now - lastCall}мс из ${ms}мс`)
}
}
}
// --- Декоратор метода: retry ---
function retry(fn, attempts = 3) {
return async function (...args) {
for (let i = 0; i < attempts; i++) {
try {
return await fn.apply(this, args)
} catch (e) {
console.log(`[retry] попытка ${i + 1} не удалась: ${e.message}`)
if (i === attempts - 1) throw e
}
}
}
}
// --- Декоратор класса: sealed (запрет изменения прототипа) ---
function sealed(Class) {
Object.seal(Class)
Object.seal(Class.prototype)
return Class
}
// --- Применение ---
class Calculator {
fibonacci(n) {
if (n <= 1) return n
return this.fibonacci(n - 1) + this.fibonacci(n - 2)
}
add(a, b) { return a + b }
}
const calc = new Calculator()
// Применяем memoize + log вручную (как decorator)
calc.fibonacci = memoize(calc.fibonacci.bind(calc))
console.log('=== Memoize: fibonacci ===')
console.log(calc.fibonacci(10)) // вычисляет
console.log(calc.fibonacci(10)) // из кэша
const loggedAdd = log(calc.add.bind(calc), 'add')
console.log('\n=== Log: add ===')
loggedAdd(3, 5)
loggedAdd(10, 20)
console.log('\n=== Throttle: search ===')
const search = throttle((query) => {
console.log('Поиск:', query)
}, 200)
search('TypeScript') // выполнится
search('декораторы') // пропустится (< 200мс)
setTimeout(() => {
search('JavaScript') // выполнится (>= 200мс)
}, 250)
console.log('\n=== Retry: fetch-like ===')
let attempts = 0
const unreliable = retry(async () => {
attempts++
if (attempts < 3) throw new Error('Сеть недоступна')
return 'Данные получены'
}, 3)
unreliable().then(result => {
console.log('Результат:', result)
console.log('Попыток потребовалось:', attempts)
})Реализуй функцию `memoize(fn)` которая оборачивает функцию и кэширует результаты по аргументам (ключ кэша — JSON.stringify(args)). Реализуй функцию `once(fn)` которая позволяет вызвать функцию только один раз — последующие вызовы возвращают результат первого вызова без повторного выполнения. Реализуй функцию `logged(fn, name)` которая логирует каждый вызов в формате "[name] вызов с аргументами: ...args" и возвращает обёртку.
memoize: const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const r = fn(...args); cache.set(key, r); return r }. once: let called = false, result; return function(...args) { if (!called) { called = true; result = fn(...args) } return result }.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке