Learning Book

Практические паттерны

Практические паттерны с Proxy

Proxy открывает мощные возможности для метапрограммирования. Рассмотрим наиболее полезные паттерны.

Валидация данных

function createValidator(target, schema) {
  return new Proxy(target, {
    set(target, prop, value, receiver) {
      if (prop in schema) {
        const validator = schema[prop]
        if (!validator(value)) {
          throw new TypeError(`Невалидное значение для ${prop}: ${value}`)
        }
      }
      return Reflect.set(target, prop, value, receiver)
    }
  })
}

const user = createValidator({}, {
  age: v => typeof v === 'number' && v >= 0 && v <= 150,
  email: v => typeof v === 'string' && v.includes('@')
})

user.age = 25      // OK
user.age = -5      // TypeError: Невалидное значение для age: -5
user.email = 'bad' // TypeError: Невалидное значение для email: bad

Значения по умолчанию

// Любое несуществующее свойство возвращает 0
const counters = new Proxy({}, {
  get(target, prop, receiver) {
    return Reflect.has(target, prop)
      ? Reflect.get(target, prop, receiver)
      : 0
  }
})

counters.visits          // 0
counters.visits++        // 1 (0 + 1)
counters.clicks++        // 1

Иммутабельный объект (глубокая заморозка)

function deepFreeze(obj) {
  return new Proxy(obj, {
    set(target, prop) {
      throw new TypeError(`Попытка изменить иммутабельное свойство: ${prop}`)
    },
    deleteProperty(target, prop) {
      throw new TypeError(`Попытка удалить иммутабельное свойство: ${prop}`)
    },
    get(target, prop, receiver) {
      const value = Reflect.get(target, prop, receiver)
      // Рекурсивно оборачиваем вложенные объекты
      if (value && typeof value === 'object') {
        return deepFreeze(value)
      }
      return value
    }
  })
}

const config = deepFreeze({ db: { host: 'localhost', port: 5432 } })
config.db.host = 'other' // TypeError!
В отличие от Object.freeze(), прокси-иммутабельность работает глубоко и даёт понятные сообщения об ошибках — ideal для конфигурационных объектов в production.

Кэширование вычислений

function memoize(target) {
  const cache = new Map()
  return new Proxy(target, {
    apply(fn, thisArg, args) {
      const key = JSON.stringify(args)
      if (cache.has(key)) {
        console.log('cache hit:', key)
        return cache.get(key)
      }
      const result = Reflect.apply(fn, thisArg, args)
      cache.set(key, result)
      return result
    }
  })
}

const expensive = memoize((n) => {
  // Дорогие вычисления...
  return n * n
})

expensive(5)  // вычисляет → 25
expensive(5)  // cache hit → 25
expensive(10) // вычисляет → 100