Добавить в корзинуПозвонить
Найти в Дзене
ovnoCod

Язык JavaScript - Флаги и дескрипторы свойств

Вы думаете, что свойство объекта - это просто пара "ключ-значение"? Что ж, это только верхушка айсберга. Под поверхностью скрывается целый мир: флаги, которые определяют, можно ли свойство изменять, перечислять или удалять. Дескрипторы, которые позволяют создавать вычисляемые свойства с геттерами и сеттерами. В JavaScript каждое свойство объекта - это не просто значение. Это структура с тремя скрытыми флагами и, возможно, функциями доступа. Понимание этой системы открывает двери к продвинутому метапрограммированию, созданию неизменяемых объектов и тонкому контролю над API. Сегодня мы разберём, как устроены свойства изнутри, как управлять их поведением и как использовать эту мощь для создания надёжных, защищённых и элегантных объектов. У каждого свойства объекта есть три скрытых флага: javascript const user = { name: "Анна" };
// Получаем дескриптор свойства
const descriptor = Object.getOwnPropertyDescriptor(user, "name");
console.log(descriptor);
// { value: "Анна", writable: true, e
Оглавление
взято с ya.ru
взято с ya.ru

Тайная жизнь свойств: Как флаги и дескрипторы управляют поведением объектов

Вы думаете, что свойство объекта - это просто пара "ключ-значение"? Что ж, это только верхушка айсберга. Под поверхностью скрывается целый мир: флаги, которые определяют, можно ли свойство изменять, перечислять или удалять. Дескрипторы, которые позволяют создавать вычисляемые свойства с геттерами и сеттерами.

В JavaScript каждое свойство объекта - это не просто значение. Это структура с тремя скрытыми флагами и, возможно, функциями доступа. Понимание этой системы открывает двери к продвинутому метапрограммированию, созданию неизменяемых объектов и тонкому контролю над API.

Сегодня мы разберём, как устроены свойства изнутри, как управлять их поведением и как использовать эту мощь для создания надёжных, защищённых и элегантных объектов.

Часть 1. Три тайных флага

У каждого свойства объекта есть три скрытых флага:

-2

javascript

const user = { name: "Анна" };

// Получаем дескриптор свойства
const descriptor = Object.getOwnPropertyDescriptor(user, "name");
console.log(descriptor);
// { value: "Анна", writable: true, enumerable: true, configurable: true }

Часть 2. Чтение и запись дескрипторов

2.1 Получение дескриптора (getOwnPropertyDescriptor)

javascript

const obj = { id: 42 };

const desc = Object.getOwnPropertyDescriptor(obj, "id");
console.log(desc.value);
// 42
console.log(desc.writable);
// true
console.log(desc.enumerable);
// true
console.log(desc.configurable);
// true

2.2 Установка дескриптора (defineProperty)

javascript

const user = {};

Object.defineProperty(user, "name", {
value: "Анна",
writable: false,
// только для чтения
enumerable: true,
configurable: true
});

console.log(user.name);
// "Анна"
user.name = "Борис";
// Ошибка в строгом режиме (тихо игнорируется в нестрогом)
console.log(user.name);
// "Анна" (не изменилось!)

Часть 3. Флаг writable - защита от изменений

javascript

const obj = {};

Object.defineProperty(obj, "constant", {
value: 42,
writable: false
// свойство только для чтения
});

obj.constant = 100;
// игнорируется (или ошибка в strict mode)
console.log(obj.constant);
// 42

// Проверка
const desc = Object.getOwnPropertyDescriptor(obj, "constant");
console.log(desc.writable);
// false

Часть 4. Флаг enumerable - скрытие от итераций

javascript

const obj = { visible: 1 };

Object.defineProperty(obj, "secret", {
value: "тайна",
enumerable: false
// не показывается в циклах
});

console.log(Object.keys(obj));
// ["visible"] (secret не виден)
console.log(Object.values(obj));
// [1]
console.log(JSON.stringify(obj));
// {"visible":1}

// Но свойство существует и доступно
console.log(obj.secret);
// "тайна"

// for...in тоже не видит secret
for (const key in obj) {
console.log(key);
// "visible"
}

// Специальный метод показывает все свойства
console.log(Object.getOwnPropertyNames(obj));
// ["visible", "secret"]

Где применяется?

  • Внутренние свойства, которые не должны мешать итерации
  • Приватные данные (до появления реальных приватных полей)
  • Мета-информация об объекте

Часть 5. Флаг configurable - защита от удаления и переопределения

configurable: false запрещает:

  • Удаление свойства (delete)
  • Изменение дескриптора (кроме writable с true на false)
  • Изменение флагов (кроме writable)

javascript

const obj = {};

Object.defineProperty(obj, "locked", {
value: 42,
configurable: false,
writable: true
});

// Нельзя удалить
delete obj.locked;
// false (или ошибка в strict mode)
console.log(obj.locked);
// 42

// Нельзя переопределить дескриптор
try {
Object.defineProperty(obj, "locked", {
configurable: true
});
} catch (e) {
console.log("Ошибка! Нельзя изменить configurable");
}

// Но можно изменить writable (только с true на false)
Object.defineProperty(obj, "locked", {
writable: false
});
// работает

// А обратно уже нельзя
try {
Object.defineProperty(obj, "locked", {
writable: true
});
} catch (e) {
console.log("Ошибка! Нельзя вернуть writable");
}

Часть 6. Геттеры и сеттеры (accessor properties)

Вместо простого значения свойство может определять функции, которые вызываются при чтении и записи.

javascript

const user = {
firstName: "Анна",
lastName: "Иванова",

get fullName() {
return `${this.firstName} ${this.lastName}`;
},

set fullName(value) {
const parts = value.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
};

console.log(user.fullName);
// "Анна Иванова" (вызвался геттер)
user.fullName = "Борис Петров";
// вызвался сеттер
console.log(user.firstName);
// "Борис"
console.log(user.lastName);
// "Петров"

6.1 Геттеры и сеттеры через defineProperty

javascript

const obj = {
_value: 0
};

Object.defineProperty(obj, "value", {
get() {
console.log("Чтение value");
return this._value;
},
set(newValue) {
console.log(`Запись value: ${newValue}`);
if (newValue < 0) {
throw new Error("Значение не может быть отрицательным");
}
this._value = newValue;
},
enumerable: true,
configurable: true
});

obj.value = 10;
// "Запись value: 10"
console.log(obj.value);
// "Чтение value" → 10
// obj.value = -5; // Ошибка!

6.2 Дескриптор доступа vs дескриптор данных

-3

Важно: Нельзя смешивать. Либо value и writable, либо get и set.

javascript

// ❌ Ошибка!
Object.defineProperty(obj, "bad", {
value: 42,
get() { return 42; }
// конфликт
});

Часть 7. Методы управления объектами

7.1 Object.preventExtensions() - запрет добавления новых свойств

javascript

const obj = { a: 1 };
Object.preventExtensions(obj);

obj.b = 2;
// игнорируется (или ошибка в strict mode)
console.log(obj.b);
// undefined

console.log(Object.isExtensible(obj));
// false

7.2 Object.seal() - запрет добавления/удаления, делает configurable: false

javascript

const obj = { a: 1, b: 2 };
Object.seal(obj);

obj.c = 3;
// игнорируется
delete obj.a;
// игнорируется
obj.b = 100;
// ✅ работает (writable остаётся true)

console.log(Object.isSealed(obj));
// true
console.log(Object.getOwnPropertyDescriptor(obj, "a").configurable);
// false

7.3 Object.freeze() - полная заморозка (ничего нельзя изменить)

javascript

const obj = { a: 1, b: { c: 2 } };
Object.freeze(obj);

obj.a = 100;
// игнорируется
obj.c = 3;
// игнорируется
delete obj.a;
// игнорируется

console.log(obj.a);
// 1

// Но freeze поверхностный!
obj.b.c = 3;
// ✅ работает! (вложенный объект не заморожен)
console.log(obj.b.c);
// 3

// Для глубокой заморозки нужна рекурсия
function deepFreeze(obj) {
Object.freeze(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
deepFreeze(obj[key]);
}
}
}

Часть 8. Сравнение методов

-4

Часть 9. Реальные кейсы

9.1 Создание констант (неизменяемых объектов)

javascript

const Constants = {};

Object.defineProperties(Constants, {
API_URL: {
value: "https://api.example.com",
writable: false,
enumerable: true,
configurable: false
},
MAX_RETRIES: {
value: 3,
writable: false,
enumerable: true,
configurable: false
},
TIMEOUT_MS: {
value: 5000,
writable: false,
enumerable: true,
configurable: false
}
});

// Constants.API_URL = "другое"; // Ошибка!
console.log(Constants.API_URL);
// "https://api.example.com"

9.2 Валидация при установке

javascript

class Temperature {
constructor() {
this._celsius = 0;
}

get celsius() {
return this._celsius;
}

set celsius(value) {
if (typeof value !== "number") {
throw new TypeError("Температура должна быть числом");
}
if (value < -273.15) {
throw new RangeError("Температура не может быть ниже абсолютного нуля");
}
this._celsius = value;
}

get fahrenheit() {
return (this._celsius * 9/5) + 32;
}

set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
}

const temp = new Temperature();
temp.celsius = 25;
console.log(temp.fahrenheit);
// 77
// temp.celsius = -300; // Ошибка!

9.3 Ленивые свойства (вычисляются при первом доступе)

javascript

function defineLazyProperty(obj, propName, getter) {
let defined = false;
let value;

Object.defineProperty(obj, propName, {
get() {
if (!defined) {
value = getter();
defined = true;
}
return value;
},
enumerable: true,
configurable: true
});
}

const data = {};
defineLazyProperty(data, "expenses", () => {
console.log("Вычисляем расходы... (тяжёлая операция)");
return [100, 200, 300, 400, 500];
});

console.log(data.expenses);
// "Вычисляем расходы..." → [100, 200, 300, 400, 500]
console.log(data.expenses);
// [100, 200, 300, 400, 500] (из кэша)

9.4 Скрытие внутренних свойств (enumerable: false)

javascript

function createBankAccount(initialBalance) {
const account = {
deposit(amount) {
if (amount > 0) {
this._balance += amount;
}
return this;
},
withdraw(amount) {
if (amount > 0 && amount <= this._balance) {
this._balance -= amount;
}
return this;
},
getBalance() {
return this._balance;
}
};

// Скрытое свойство
Object.defineProperty(account, "_balance", {
value: initialBalance,
writable: true,
enumerable: false,
// не видно в циклах
configurable: false
});

return account;
}

const account = createBankAccount(1000);
account.deposit(500);
console.log(account.getBalance());
// 1500
console.log(Object.keys(account));
// ["deposit", "withdraw", "getBalance"] (_balance не видно)

Часть 10. Подводные камни

Камень #1: configurable: false нельзя отменить

javascript

const obj = {};
Object.defineProperty(obj, "prop", {
value: 42,
configurable: false
});

// Нельзя сделать configurable: true
// Нельзя удалить свойство
// Нельзя изменить дескриптор (кроме writable с true на false)

Камень #2: freeze и seal поверхностные

javascript

const obj = { a: { b: 1 } };
Object.freeze(obj);

obj.a.b = 2;
// работает!
console.log(obj.a.b);
// 2

Камень #3: Геттер не должен иметь побочных эффектов (или должен, но осторожно)

javascript

let callCount = 0;
const obj = {
get count() {
callCount++;
return callCount;
}
};

console.log(obj.count);
// 1
console.log(obj.count);
// 2
console.log(obj.count);
// 3 (каждый вызов меняет состояние!)

Часть 11. Продвинутая техника: Object.defineProperties

javascript

const library = {};

Object.defineProperties(library, {
// Свойство с геттером и сеттером
version: {
get() { return this._version; },
set(v) { this._version = v; },
enumerable: true
},

// Только для чтения
author: {
value: "Анна Иванова",
writable: false,
enumerable: true
},

// Скрытое свойство
_version: {
value: "1.0.0",
enumerable: false,
writable: true
}
});

library.version = "2.0.0";
console.log(library.version);
// "2.0.0"
console.log(library.author);
// "Анна Иванова"
console.log(Object.keys(library));
// ["version", "author"]

Итог: Манифест флагов и дескрипторов

  1. Три флага - writable, enumerable, configurable - управляют поведением свойства.
  2. writable: false - свойство только для чтения.
  3. enumerable: false - свойство скрыто от итераций (Object.keys, for...in).
  4. configurable: false - свойство нельзя удалить или изменить дескриптор.
  5. Геттеры/сеттеры - позволяют вычислять значения при доступе и валидировать при записи.
  6. Object.freeze() - полная заморозка (поверхностная).
  7. Object.seal() - запрет добавления/удаления.
  8. Object.preventExtensions() - запрет добавления новых свойств.
  9. Дескрипторы данных и доступа - взаимоисключающие.

Финальный тест (что выведет?):

javascript

const obj = {};

Object.defineProperty(obj, "a", {
value: 1,
writable: false,
enumerable: true,
configurable: true
});

Object.defineProperty(obj, "b", {
value: 2,
writable: true,
enumerable: false,
configurable: false
});

obj.a = 10;
obj.b = 20;
delete obj.b;

console.log(obj.a);
console.log(obj.b);
console.log(Object.keys(obj));

Ответы:

  • 1 (writable: false)
  • 20 (b остался, delete не сработал из-за configurable: false)
  • ["a"] (b не enumerable)

Флаги и дескрипторы - это инструмент для тех, кто хочет контролировать каждое свойство своего объекта. Они позволяют создавать неизменяемые константы, скрывать внутренние детали, валидировать данные и создавать вычисляемые свойства. Это не повседневный инструмент, но когда он нужен - он незаменим. Используйте эту мощь с умом, и ваши объекты станут не только хранилищами данных, но и умными, защищёнными структурами.