В SDK для платёжного процессора нужно сделать так, чтобы config.API_URL нельзя было случайно перезаписать, а служебное поле _buildId не попадало в JSON.stringify или Object.keys. Обычные свойства это не умеют — но дескрипторы умеют.
for...in и Object.keysJSON.stringify игнорирует non-enumerable свойстваObject.keys возвращает только enumerable свойстваПолучить дескриптор конкретного свойства:
const user = { name: 'Иван', age: 30 }
Object.getOwnPropertyDescriptor(user, 'name')
// {
// value: 'Иван',
// writable: true, — можно изменять
// enumerable: true, — виден в for...in и Object.keys()
// configurable: true — можно удалять и переопределять
// }for...in, Object.keys(), JSON.stringify()Создаёт или изменяет свойство с явным заданием флагов:
const config = {}
Object.defineProperty(config, 'API_URL', {
value: 'https://api.example.com',
writable: false, // нельзя перезаписать
enumerable: true,
configurable: false, // нельзя удалить или переопределить
})
config.API_URL = 'другой URL' // В strict mode — TypeError! Молча игнорируется иначе.
console.log(config.API_URL) // 'https://api.example.com'const app = {}
Object.defineProperties(app, {
version: { value: '2.1.0', writable: false, enumerable: true, configurable: false },
_internalId: { value: 'x9f2', writable: false, enumerable: false, configurable: false },
})
Object.keys(app) // ['version'] — _internalId не виден// freeze — полная заморозка: нельзя добавлять, удалять или изменять
const settings = Object.freeze({ theme: 'dark', lang: 'ru' })
settings.theme = 'light' // молча игнорируется (или TypeError в strict)
settings.newProp = 'x' // то же самое
console.log(settings.theme) // 'dark'
// seal — нельзя добавлять или удалять, но можно изменять
const profile = Object.seal({ name: 'Иван', score: 0 })
profile.score = 100 // OK — изменение разрешено
profile.email = 'x' // игнорируется — добавление запрещеноconst obj = { a: 1 }
Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false })
Object.keys(obj) // ['a'] — только enumerable
Object.getOwnPropertyNames(obj) // ['a', 'hidden'] — все, включая non-enumerable1. Забыть, что defineProperty без `writable: true` создаёт read-only свойство по умолчанию:
// Плохо: хотели обычное свойство, но получили read-only
const obj = {}
Object.defineProperty(obj, 'name', { value: 'Иван' })
// writable по умолчанию = false!
obj.name = 'Пётр'
console.log(obj.name) // 'Иван' — не изменилось!
// Хорошо: явно укажи writable
Object.defineProperty(obj, 'name', {
value: 'Иван',
writable: true,
enumerable: true,
configurable: true,
})2. Object.freeze замораживает только первый уровень (shallow freeze):
const config = Object.freeze({
db: { host: 'localhost', port: 5432 }
})
config.db = {} // игнорируется — первый уровень заморожен
config.db.port = 9999 // РАБОТАЕТ — вложенный объект не заморожен!
console.log(config.db.port) // 9999
// Для глубокой заморозки нужна рекурсия:
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(name => {
if (typeof obj[name] === 'object' && obj[name] !== null) {
deepFreeze(obj[name])
}
})
return Object.freeze(obj)
}3. configurable: false нельзя отменить:
const obj = {}
Object.defineProperty(obj, 'id', { value: 1, configurable: false })
// Попытка изменить дескриптор — TypeError
Object.defineProperty(obj, 'id', { writable: true })
// TypeError: Cannot redefine property: idAPI_URL, API_KEY делают read-only через writable: falseHTTP с non-configurable полями вместо const с объектом_version, _buildId как non-enumerable: не попадают в JSON и логиenumerable: false, чтобы не ломать for...inКонфиг приложения с read-only полями и скрытыми метаданными через defineProperty
// Конфигурация приложения с защищёнными полями
const appConfig = {
apiUrl: 'https://api.myapp.ru',
timeout: 5000,
}
// Добавляем скрытое служебное поле (не попадёт в JSON и Object.keys)
Object.defineProperty(appConfig, '_buildId', {
value: 'build-20240315-abc',
writable: false,
enumerable: false, // скрыто от перечисления
configurable: false,
})
// Делаем apiUrl read-only (критически важный параметр)
Object.defineProperty(appConfig, 'apiUrl', {
value: appConfig.apiUrl,
writable: false,
enumerable: true,
configurable: false,
})
console.log('Ключи конфига:', Object.keys(appConfig))
// ['apiUrl', 'timeout'] — _buildId скрыт
console.log('Все свойства:', Object.getOwnPropertyNames(appConfig))
// ['apiUrl', 'timeout', '_buildId']
console.log('JSON:', JSON.stringify(appConfig))
// '{"apiUrl":"https://api.myapp.ru","timeout":5000}' — _buildId не включён
// Попытка изменить read-only поле — молча игнорируется
appConfig.apiUrl = 'https://hacker.example.com'
console.log(appConfig.apiUrl) // 'https://api.myapp.ru' — не изменилось!
// Служебное поле доступно напрямую (если знаешь имя)
console.log(appConfig._buildId) // 'build-20240315-abc'
// Дескриптор
const desc = Object.getOwnPropertyDescriptor(appConfig, 'apiUrl')
console.log(desc.writable) // false
console.log(desc.configurable) // false
console.log(desc.enumerable) // trueВ SDK для платёжного процессора нужно сделать так, чтобы config.API_URL нельзя было случайно перезаписать, а служебное поле _buildId не попадало в JSON.stringify или Object.keys. Обычные свойства это не умеют — но дескрипторы умеют.
for...in и Object.keysJSON.stringify игнорирует non-enumerable свойстваObject.keys возвращает только enumerable свойстваПолучить дескриптор конкретного свойства:
const user = { name: 'Иван', age: 30 }
Object.getOwnPropertyDescriptor(user, 'name')
// {
// value: 'Иван',
// writable: true, — можно изменять
// enumerable: true, — виден в for...in и Object.keys()
// configurable: true — можно удалять и переопределять
// }for...in, Object.keys(), JSON.stringify()Создаёт или изменяет свойство с явным заданием флагов:
const config = {}
Object.defineProperty(config, 'API_URL', {
value: 'https://api.example.com',
writable: false, // нельзя перезаписать
enumerable: true,
configurable: false, // нельзя удалить или переопределить
})
config.API_URL = 'другой URL' // В strict mode — TypeError! Молча игнорируется иначе.
console.log(config.API_URL) // 'https://api.example.com'const app = {}
Object.defineProperties(app, {
version: { value: '2.1.0', writable: false, enumerable: true, configurable: false },
_internalId: { value: 'x9f2', writable: false, enumerable: false, configurable: false },
})
Object.keys(app) // ['version'] — _internalId не виден// freeze — полная заморозка: нельзя добавлять, удалять или изменять
const settings = Object.freeze({ theme: 'dark', lang: 'ru' })
settings.theme = 'light' // молча игнорируется (или TypeError в strict)
settings.newProp = 'x' // то же самое
console.log(settings.theme) // 'dark'
// seal — нельзя добавлять или удалять, но можно изменять
const profile = Object.seal({ name: 'Иван', score: 0 })
profile.score = 100 // OK — изменение разрешено
profile.email = 'x' // игнорируется — добавление запрещеноconst obj = { a: 1 }
Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false })
Object.keys(obj) // ['a'] — только enumerable
Object.getOwnPropertyNames(obj) // ['a', 'hidden'] — все, включая non-enumerable1. Забыть, что defineProperty без `writable: true` создаёт read-only свойство по умолчанию:
// Плохо: хотели обычное свойство, но получили read-only
const obj = {}
Object.defineProperty(obj, 'name', { value: 'Иван' })
// writable по умолчанию = false!
obj.name = 'Пётр'
console.log(obj.name) // 'Иван' — не изменилось!
// Хорошо: явно укажи writable
Object.defineProperty(obj, 'name', {
value: 'Иван',
writable: true,
enumerable: true,
configurable: true,
})2. Object.freeze замораживает только первый уровень (shallow freeze):
const config = Object.freeze({
db: { host: 'localhost', port: 5432 }
})
config.db = {} // игнорируется — первый уровень заморожен
config.db.port = 9999 // РАБОТАЕТ — вложенный объект не заморожен!
console.log(config.db.port) // 9999
// Для глубокой заморозки нужна рекурсия:
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(name => {
if (typeof obj[name] === 'object' && obj[name] !== null) {
deepFreeze(obj[name])
}
})
return Object.freeze(obj)
}3. configurable: false нельзя отменить:
const obj = {}
Object.defineProperty(obj, 'id', { value: 1, configurable: false })
// Попытка изменить дескриптор — TypeError
Object.defineProperty(obj, 'id', { writable: true })
// TypeError: Cannot redefine property: idAPI_URL, API_KEY делают read-only через writable: falseHTTP с non-configurable полями вместо const с объектом_version, _buildId как non-enumerable: не попадают в JSON и логиenumerable: false, чтобы не ломать for...inКонфиг приложения с read-only полями и скрытыми метаданными через defineProperty
// Конфигурация приложения с защищёнными полями
const appConfig = {
apiUrl: 'https://api.myapp.ru',
timeout: 5000,
}
// Добавляем скрытое служебное поле (не попадёт в JSON и Object.keys)
Object.defineProperty(appConfig, '_buildId', {
value: 'build-20240315-abc',
writable: false,
enumerable: false, // скрыто от перечисления
configurable: false,
})
// Делаем apiUrl read-only (критически важный параметр)
Object.defineProperty(appConfig, 'apiUrl', {
value: appConfig.apiUrl,
writable: false,
enumerable: true,
configurable: false,
})
console.log('Ключи конфига:', Object.keys(appConfig))
// ['apiUrl', 'timeout'] — _buildId скрыт
console.log('Все свойства:', Object.getOwnPropertyNames(appConfig))
// ['apiUrl', 'timeout', '_buildId']
console.log('JSON:', JSON.stringify(appConfig))
// '{"apiUrl":"https://api.myapp.ru","timeout":5000}' — _buildId не включён
// Попытка изменить read-only поле — молча игнорируется
appConfig.apiUrl = 'https://hacker.example.com'
console.log(appConfig.apiUrl) // 'https://api.myapp.ru' — не изменилось!
// Служебное поле доступно напрямую (если знаешь имя)
console.log(appConfig._buildId) // 'build-20240315-abc'
// Дескриптор
const desc = Object.getOwnPropertyDescriptor(appConfig, 'apiUrl')
console.log(desc.writable) // false
console.log(desc.configurable) // false
console.log(desc.enumerable) // trueВ сервисе конфигурации нужна функция makeReadOnly(obj), которая клонирует объект и делает все его свойства non-writable и non-configurable. Исходный объект должен остаться неизменным.
Object.getOwnPropertyDescriptors(obj) возвращает объект со всеми дескрипторами. Перебери ключи и для каждого поставь writable: false, configurable: false. Затем Object.defineProperties({}, descriptors) создаст новый объект с этими дескрипторами.