Reflect #
Reflect — встроенный объект с методами, дублирующими низкоуровневые операции над объектами: доступ к свойствам, присваивание, in, дескрипторы, прототип. Методы возвращают предсказуемые результаты (boolean или значение) вместо смешения return/throw у старых API. В ловушках Proxy Reflect — стандартный способ передать вызов target.
Зачем Reflect, если есть операторы #
| Операция | Оператор / старый API | Reflect |
|---|---|---|
| Чтение | obj[key] | Reflect.get(obj, key, receiver) |
| Запись | obj[key] = v | Reflect.set(obj, key, v, receiver) |
in | key in obj | Reflect.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 |
deleteProperty | delete |
ownKeys | все собственные ключи |
defineProperty | дескриптор свойства |
getOwnPropertyDescriptor | чтение дескриптора |
preventExtensions / isExtensible | расширяемость |
getPrototypeOf / setPrototypeOf | прототип |
apply / construct | вызов функции / new |
Итог #
Reflectдублирует метаоперации над объектами функциональным API с предсказуемым возвратом.- В ловушках Proxy вызывайте соответствующий
Reflect.*для делегирования target. receiverвget/setобязателен при геттерах/сеттерах и наследовании.- Пара Proxy + Reflect — идиоматичный паттерн валидации и контроля доступа в JSFlow.