В форме регистрации ты хочешь, чтобы при записи некорректного email немедленно бросалась ошибка — ещё до отправки на сервер. Можно добавить проверку в каждый сеттер вручную, а можно обернуть объект данных в Proxy и перехватывать все записи в одном месте. Так работает реактивность во Vue 3.
Прокси создаётся с двумя аргументами: целевой объект target и обработчик handler с ловушками:
const target = { name: 'Иван' }
const proxy = new Proxy(target, {
get(target, prop) {
console.log(`Читаем: ${prop}`)
return target[prop]
}
})
console.log(proxy.name) // Читаем: name → 'Иван'const config = new Proxy({}, {
get(target, prop) {
if (prop in target) return target[prop]
return `Свойство '${prop}' не найдено` // дефолт вместо undefined
}
})
config.apiUrl = 'https://api.example.com'
console.log(config.apiUrl) // 'https://api.example.com'
console.log(config.timeout) // "Свойство 'timeout' не найдено"const userSchema = new Proxy({}, {
set(target, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number') throw new TypeError('Возраст должен быть числом')
if (value < 0 || value > 150) throw new RangeError(`Недопустимый возраст: ${value}`)
}
target[prop] = value
return true // обязательно возвращать true при успехе
}
})
userSchema.name = 'Мария' // OK
userSchema.age = 25 // OK
// userSchema.age = -5 // RangeError!const hiddenProps = new Proxy({ _secret: 'abc', public: 'data' }, {
has(target, prop) {
if (prop.startsWith('_')) return false // скрываем приватные свойства
return prop in target
}
})
console.log('public' in hiddenProps) // true
console.log('_secret' in hiddenProps) // false — скрыто!// Запрет удаления важных ключей:
const locked = new Proxy({ id: 1, name: 'Продукт' }, {
deleteProperty(target, prop) {
if (prop === 'id') throw new Error('Нельзя удалить id!')
return delete target[prop]
}
})
// Перехват вызова функции:
function multiply(a, b) { return a * b }
const loggedMultiply = new Proxy(multiply, {
apply(target, thisArg, args) {
console.log(`Вызов с аргументами: ${args}`)
return target.apply(thisArg, args)
}
})
console.log(loggedMultiply(3, 4)) // Вызов с аргументами: 3,4 → 12Reflect предоставляет те же операции, что и стандартные JS-операторы, но в виде методов. Удобно внутри ловушек Proxy: перехватываешь операцию, делаешь что нужно, передаёшь дальше:
const obj = { value: 42 }
const loggingProxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log(`GET: ${prop}`)
return Reflect.get(target, prop, receiver) // аналог target[prop]
},
set(target, prop, value, receiver) {
console.log(`SET: ${prop} = ${JSON.stringify(value)}`)
return Reflect.set(target, prop, value, receiver) // аналог target[prop] = value
}
})function createFlexArray(...items) {
return new Proxy(items, {
get(target, prop) {
const index = Number(prop)
if (Number.isInteger(index) && index < 0) {
return target[target.length + index]
}
return Reflect.get(target, prop)
}
})
}
const tags = createFlexArray('новый', 'активный', 'завершён', 'архив')
console.log(tags[-1]) // 'архив'
console.log(tags[-2]) // 'завершён'
console.log(tags[0]) // 'новый'1. Забыть вернуть true из ловушки set — в strict mode будет TypeError:
// Плохо: ловушка set ничего не возвращает
const p = new Proxy({}, {
set(target, prop, value) {
target[prop] = value
// забыли return true!
}
})
// В strict mode: TypeError: 'set' on proxy: trap returned falsish
// Хорошо: всегда return true
const p2 = new Proxy({}, {
set(target, prop, value) {
target[prop] = value
return true
}
})2. Прокси вокруг примитива — TypeError:
// Плохо: Proxy работает только с объектами
const p = new Proxy(42, {}) // TypeError: Cannot create proxy with a non-object as target
// Хорошо: target должен быть объектом или функцией
const p2 = new Proxy({ value: 42 }, {})3. Бесконечная рекурсия — чтение target[prop] внутри get вместо Reflect.get:
// Плохо: если target — тоже прокси, можно попасть в рекурсию
const p = new Proxy(obj, {
get(target, prop) {
return target[prop] // потенциальная рекурсия при вложенных прокси
}
})
// Хорошо: используй Reflect.get с receiver
const p2 = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver) // корректно обрабатывает цепочку
}
})Валидирующий прокси — выбрасывает ошибку при записи некорректных значений в профиль пользователя
// Схема валидации для профиля пользователя
const userValidators = {
name(value) {
if (typeof value !== 'string') throw new TypeError('Имя должно быть строкой')
if (value.trim().length < 2) throw new RangeError('Имя слишком короткое')
},
age(value) {
if (typeof value !== 'number') throw new TypeError('Возраст должен быть числом')
if (value < 0 || value > 150) throw new RangeError(`Недопустимый возраст: ${value}`)
},
email(value) {
if (typeof value !== 'string') throw new TypeError('Email должен быть строкой')
if (!/^[^@]+@[^@]+\.[^@]+$/.test(value)) throw new Error(`Некорректный email: ${value}`)
},
balance(value) {
if (typeof value !== 'number') throw new TypeError('Баланс должен быть числом')
if (value < 0) throw new RangeError('Баланс не может быть отрицательным')
},
}
function createValidatedProfile(validators) {
const data = {}
return new Proxy(data, {
set(target, prop, value) {
if (validators[prop]) {
validators[prop](value) // выбрасывает ошибку если невалидно
}
return Reflect.set(target, prop, value)
},
get(target, prop) {
if (prop === 'toJSON') {
return () => ({ ...target })
}
return Reflect.get(target, prop)
}
})
}
const profile = createValidatedProfile(userValidators)
// Корректные данные
profile.name = 'Елена Иванова'
profile.age = 32
profile.email = 'elena@company.ru'
profile.balance = 15000
profile.city = 'Москва' // нет валидатора — записывается свободно
console.log(profile.name) // 'Елена Иванова'
console.log(profile.age) // 32
console.log(profile.balance) // 15000
// Некорректные данные — ловим ошибки
const errors = []
try { profile.age = -5 }
catch (e) { errors.push(`age=-5: ${e.message}`) }
try { profile.email = 'не-email' }
catch (e) { errors.push(`email='не-email': ${e.message}`) }
try { profile.balance = -100 }
catch (e) { errors.push(`balance=-100: ${e.message}`) }
try { profile.name = 'А' }
catch (e) { errors.push(`name='А': ${e.message}`) }
errors.forEach(msg => console.log('Ошибка:', msg))
// Ошибка: age=-5: Недопустимый возраст: -5
// Ошибка: email='не-email': Некорректный email: не-email
// Ошибка: balance=-100: Баланс не может быть отрицательным
// Ошибка: name='А': Имя слишком короткое
// Данные не изменились после ошибок:
console.log(profile.age) // 32
console.log(profile.balance) // 15000В форме регистрации ты хочешь, чтобы при записи некорректного email немедленно бросалась ошибка — ещё до отправки на сервер. Можно добавить проверку в каждый сеттер вручную, а можно обернуть объект данных в Proxy и перехватывать все записи в одном месте. Так работает реактивность во Vue 3.
Прокси создаётся с двумя аргументами: целевой объект target и обработчик handler с ловушками:
const target = { name: 'Иван' }
const proxy = new Proxy(target, {
get(target, prop) {
console.log(`Читаем: ${prop}`)
return target[prop]
}
})
console.log(proxy.name) // Читаем: name → 'Иван'const config = new Proxy({}, {
get(target, prop) {
if (prop in target) return target[prop]
return `Свойство '${prop}' не найдено` // дефолт вместо undefined
}
})
config.apiUrl = 'https://api.example.com'
console.log(config.apiUrl) // 'https://api.example.com'
console.log(config.timeout) // "Свойство 'timeout' не найдено"const userSchema = new Proxy({}, {
set(target, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number') throw new TypeError('Возраст должен быть числом')
if (value < 0 || value > 150) throw new RangeError(`Недопустимый возраст: ${value}`)
}
target[prop] = value
return true // обязательно возвращать true при успехе
}
})
userSchema.name = 'Мария' // OK
userSchema.age = 25 // OK
// userSchema.age = -5 // RangeError!const hiddenProps = new Proxy({ _secret: 'abc', public: 'data' }, {
has(target, prop) {
if (prop.startsWith('_')) return false // скрываем приватные свойства
return prop in target
}
})
console.log('public' in hiddenProps) // true
console.log('_secret' in hiddenProps) // false — скрыто!// Запрет удаления важных ключей:
const locked = new Proxy({ id: 1, name: 'Продукт' }, {
deleteProperty(target, prop) {
if (prop === 'id') throw new Error('Нельзя удалить id!')
return delete target[prop]
}
})
// Перехват вызова функции:
function multiply(a, b) { return a * b }
const loggedMultiply = new Proxy(multiply, {
apply(target, thisArg, args) {
console.log(`Вызов с аргументами: ${args}`)
return target.apply(thisArg, args)
}
})
console.log(loggedMultiply(3, 4)) // Вызов с аргументами: 3,4 → 12Reflect предоставляет те же операции, что и стандартные JS-операторы, но в виде методов. Удобно внутри ловушек Proxy: перехватываешь операцию, делаешь что нужно, передаёшь дальше:
const obj = { value: 42 }
const loggingProxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log(`GET: ${prop}`)
return Reflect.get(target, prop, receiver) // аналог target[prop]
},
set(target, prop, value, receiver) {
console.log(`SET: ${prop} = ${JSON.stringify(value)}`)
return Reflect.set(target, prop, value, receiver) // аналог target[prop] = value
}
})function createFlexArray(...items) {
return new Proxy(items, {
get(target, prop) {
const index = Number(prop)
if (Number.isInteger(index) && index < 0) {
return target[target.length + index]
}
return Reflect.get(target, prop)
}
})
}
const tags = createFlexArray('новый', 'активный', 'завершён', 'архив')
console.log(tags[-1]) // 'архив'
console.log(tags[-2]) // 'завершён'
console.log(tags[0]) // 'новый'1. Забыть вернуть true из ловушки set — в strict mode будет TypeError:
// Плохо: ловушка set ничего не возвращает
const p = new Proxy({}, {
set(target, prop, value) {
target[prop] = value
// забыли return true!
}
})
// В strict mode: TypeError: 'set' on proxy: trap returned falsish
// Хорошо: всегда return true
const p2 = new Proxy({}, {
set(target, prop, value) {
target[prop] = value
return true
}
})2. Прокси вокруг примитива — TypeError:
// Плохо: Proxy работает только с объектами
const p = new Proxy(42, {}) // TypeError: Cannot create proxy with a non-object as target
// Хорошо: target должен быть объектом или функцией
const p2 = new Proxy({ value: 42 }, {})3. Бесконечная рекурсия — чтение target[prop] внутри get вместо Reflect.get:
// Плохо: если target — тоже прокси, можно попасть в рекурсию
const p = new Proxy(obj, {
get(target, prop) {
return target[prop] // потенциальная рекурсия при вложенных прокси
}
})
// Хорошо: используй Reflect.get с receiver
const p2 = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver) // корректно обрабатывает цепочку
}
})Валидирующий прокси — выбрасывает ошибку при записи некорректных значений в профиль пользователя
// Схема валидации для профиля пользователя
const userValidators = {
name(value) {
if (typeof value !== 'string') throw new TypeError('Имя должно быть строкой')
if (value.trim().length < 2) throw new RangeError('Имя слишком короткое')
},
age(value) {
if (typeof value !== 'number') throw new TypeError('Возраст должен быть числом')
if (value < 0 || value > 150) throw new RangeError(`Недопустимый возраст: ${value}`)
},
email(value) {
if (typeof value !== 'string') throw new TypeError('Email должен быть строкой')
if (!/^[^@]+@[^@]+\.[^@]+$/.test(value)) throw new Error(`Некорректный email: ${value}`)
},
balance(value) {
if (typeof value !== 'number') throw new TypeError('Баланс должен быть числом')
if (value < 0) throw new RangeError('Баланс не может быть отрицательным')
},
}
function createValidatedProfile(validators) {
const data = {}
return new Proxy(data, {
set(target, prop, value) {
if (validators[prop]) {
validators[prop](value) // выбрасывает ошибку если невалидно
}
return Reflect.set(target, prop, value)
},
get(target, prop) {
if (prop === 'toJSON') {
return () => ({ ...target })
}
return Reflect.get(target, prop)
}
})
}
const profile = createValidatedProfile(userValidators)
// Корректные данные
profile.name = 'Елена Иванова'
profile.age = 32
profile.email = 'elena@company.ru'
profile.balance = 15000
profile.city = 'Москва' // нет валидатора — записывается свободно
console.log(profile.name) // 'Елена Иванова'
console.log(profile.age) // 32
console.log(profile.balance) // 15000
// Некорректные данные — ловим ошибки
const errors = []
try { profile.age = -5 }
catch (e) { errors.push(`age=-5: ${e.message}`) }
try { profile.email = 'не-email' }
catch (e) { errors.push(`email='не-email': ${e.message}`) }
try { profile.balance = -100 }
catch (e) { errors.push(`balance=-100: ${e.message}`) }
try { profile.name = 'А' }
catch (e) { errors.push(`name='А': ${e.message}`) }
errors.forEach(msg => console.log('Ошибка:', msg))
// Ошибка: age=-5: Недопустимый возраст: -5
// Ошибка: email='не-email': Некорректный email: не-email
// Ошибка: balance=-100: Баланс не может быть отрицательным
// Ошибка: name='А': Имя слишком короткое
// Данные не изменились после ошибок:
console.log(profile.age) // 32
console.log(profile.balance) // 15000В конфигурационном модуле нужна защита: после загрузки настройки нельзя менять. Напиши функцию readOnly(obj), которая возвращает Proxy, где любая попытка установить или удалить свойство выбрасывает TypeError с сообщением "Object is read-only". Чтение свойств должно работать нормально.
В ловушках set и deleteProperty просто выбрасывай throw new TypeError('Object is read-only'). Ловушку get не трогаем — чтение работает стандартно через target[prop]. Не забудь вернуть true из set если нужно разрешить запись (здесь не нужно — сразу throw).