Reflect

Reflect #

Reflect — встроенный объект с методами, дублирующими низкоуровневые операции над объектами: доступ к свойствам, присваивание, in, дескрипторы, прототип. Методы возвращают предсказуемые результаты (boolean или значение) вместо смешения return/throw у старых API. В ловушках Proxy Reflect — стандартный способ передать вызов target.


Зачем Reflect, если есть операторы #

ОперацияОператор / старый APIReflect
Чтениеobj[key]Reflect.get(obj, key, receiver)
Записьobj[key] = vReflect.set(obj, key, v, receiver)
inkey in objReflect.has(obj, key)
Удалениеdelete obj[key]Reflect.deleteProperty(obj, key)
КлючиObject.keys (отдельно)Reflect.ownKeys(obj)

Преимущества в метапрограммировании:

  • единый функциональный стиль в ловушках Proxy;
  • Reflect.set / Reflect.defineProperty возвращают false при неудаче вместо throw в non-strict;
  • явный параметр receiver для корректной цепочки прототипов.

Reflect.get и Reflect.set #

const pricing = {
  base: 1500,
  get total() {
    return this.base + (this.delivery ?? 0)
  },
}

const wrapper = { base: 1500, delivery: 300 }

Reflect.get(pricing, 'total', wrapper) // 1800 — геттер видит wrapper как this
Reflect.get(pricing, 'total') // NaN или иное — this без delivery

В ловушке get proxy всегда передавайте receiver:

const order = { amount: 1000 }

const p = new Proxy(order, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver)
  },
})

Reflect.set аналогично:

Reflect.set(order, 'amount', 1200) // true
order.amount // 1200

Reflect.has #

Эквивалент 'prop' in obj:

const shipment = { trackId: 'TRK-9X2', carrier: 'express' }

Reflect.has(shipment, 'trackId') // true
Reflect.has(shipment, 'eta') // false

В proxy с фильтрацией внутренних полей:

function hideUnderscore(obj) {
  return new Proxy(obj, {
    has(target, prop) {
      if (String(prop).startsWith('_')) return false
      return Reflect.has(target, prop)
    },
  })
}

Reflect.deleteProperty #

const draft = { note: 'позвонить', phone: '+79991112233' }

Reflect.deleteProperty(draft, 'note') // true
Reflect.has(draft, 'note') // false

В ловушке deleteProperty proxy:

const locked = new Proxy({ status: 'paid' }, {
  deleteProperty(target, prop) {
    if (prop === 'status') return false
    return Reflect.deleteProperty(target, prop)
  },
})

Reflect.ownKeys #

Возвращает все собственные ключи — строки, символы, в порядке спецификации (как для Object.getOwnPropertyNames + getOwnPropertySymbols):

const cartMeta = {
  id: 'cart-1',
  [Symbol.for('cart.version')]: 2,
}

Reflect.ownKeys(cartMeta)
// ['id', Symbol(cart.version)]

Используется в ловушке ownKeys при виртуализации списка полей.


Reflect.defineProperty #

Низкоуровневое определение свойства с дескриптором:

const catalog = {}

Reflect.defineProperty(catalog, 'featuredSku', {
  value: 'KB-01',
  writable: false,
  enumerable: true,
  configurable: false,
})

Возвращает true/false — удобно в валидирующем proxy:

function createStrictStore() {
  const data = {}
  return new Proxy(data, {
    defineProperty(target, prop, desc) {
      if (desc.writable === false && desc.configurable === false) {
        return Reflect.defineProperty(target, prop, desc)
      }
      throw new TypeError('только frozen-свойства')
    },
  })
}

Reflect.apply и Reflect.construct #

Для функций-target в Proxy:

function sum(a, b) {
  return a + b
}

Reflect.apply(sum, null, [2, 3]) // 5

function LineItem(sku, qty) {
  this.sku = sku
  this.qty = qty
}

const item = Reflect.construct(LineItem, ['HD-07', 1])
// эквивалент new LineItem('HD-07', 1)

Ловушки apply и construct в proxy вызывают эти методы для прозрачного делегирования.


Reflect.getPrototypeOf / setPrototypeOf #

const proto = { label() { return 'playlist' } }
const playlist = { title: 'Focus' }

Reflect.setPrototypeOf(playlist, proto)
Reflect.getPrototypeOf(playlist) === proto // true

Изменение прототипа через proxy редко; чаще set/get на данных API.


Практика: унифицированный доступ к конфигу доставки #

function createConfigProxy(config, schema) {
  return new Proxy(config, {
    get(target, prop, receiver) {
      if (typeof prop === 'string' && schema[prop]?.readonly) {
        const value = Reflect.get(target, prop, receiver)
        return Object.freeze(typeof value === 'object' && value ? { ...value } : value)
      }
      return Reflect.get(target, prop, receiver)
    },
    set(target, prop, value, receiver) {
      const rule = schema[prop]
      if (!rule) return false
      if (rule.readonly) return false
      if (rule.type === 'number' && typeof value !== 'number') return false
      return Reflect.set(target, prop, value, receiver)
    },
    has(target, prop) {
      return Reflect.has(schema, prop)
    },
  })
}

const schema = {
  expressFee: { type: 'number', readonly: false },
  currency: { type: 'string', readonly: true },
}

const cfg = createConfigProxy({ expressFee: 590, currency: 'RUB' }, schema)
cfg.expressFee = 650 // true
cfg.currency = 'USD' // false в non-strict; TypeError в strict
'expressFee' in cfg // true
'unknown' in cfg // false

Сводка методов Reflect #

МетодНазначение
get / setчтение / запись свойства
hasоператор in
deletePropertydelete
ownKeysвсе собственные ключи
definePropertyдескриптор свойства
getOwnPropertyDescriptorчтение дескриптора
preventExtensions / isExtensibleрасширяемость
getPrototypeOf / setPrototypeOfпрототип
apply / constructвызов функции / new

Итог #

  • Reflect дублирует метаоперации над объектами функциональным API с предсказуемым возвратом.
  • В ловушках Proxy вызывайте соответствующий Reflect.* для делегирования target.
  • receiver в get/set обязателен при геттерах/сеттерах и наследовании.
  • Пара Proxy + Reflect — идиоматичный паттерн валидации и контроля доступа в JSFlow.

См. также #