Property Descriptor
Property lar orqasidagi yashirin metadata — writable, enumerable, configurable, accessor descriptor, freeze/seal va extensible.
23-dars
Property Descriptor
1. Descriptor nima o'zi?
const user = { name: 'Ali' };
Siz user.name ni oddiy "qiymat" deb o'ylaysiz. Lekin JS spetsifikatsiyasi (ECMA-262) da har bir property aslida yashirin metadata bilan birga saqlanadi. Bu metadata Property Descriptor deyiladi.
Ko'rish uchun:
const user = { name: 'Ali' };
console.log(Object.getOwnPropertyDescriptor(user, 'name'));
// {
// value: 'Ali',
// writable: true,
// enumerable: true,
// configurable: true
// }
Demak, name: 'Ali' yozganingizda, JS engine sizga ko'rinmagan holda 4 ta flag qo'shadi. Bular — property ning "huquqlari".
2. Ikki xil Descriptor turi bor
JS da property ikki xil bo'lishi mumkin:
A) Data Descriptor — oddiy qiymat saqlaydi:
{ value, writable, enumerable, configurable }
B) Accessor Descriptor — getter/setter orqali ishlaydi:
{ get, set, enumerable, configurable }
Muhim qoida: bitta property ikkalasi ham bo'la olmaydi. Yoki value bor, yoki get/set bor — ikkalasi birga bo'lsa, TypeError chiqadi.
Object.defineProperty(obj, 'x', {
value: 10,
get() { return 20; } // ❌ TypeError: Invalid property descriptor
});
3. Har bir flag nima qiladi
writable
Property ning qiymatini o'zgartirish mumkinmi, degan savol.
const obj = {};
Object.defineProperty(obj, 'x', {
value: 10,
writable: false,
enumerable: true,
configurable: true
});
obj.x = 99; // sokin ignore qilinadi (strict mode da TypeError)
console.log(obj.x); // 10
Strict modeda ('use strict' yoki module/class ichida — ular default strict) bu operatsiya xato tashlaydi:
'use strict';
obj.x = 99; // TypeError: Cannot assign to read only property 'x'
enumerable
Property for...in, Object.keys(), JSON.stringify() kabi iteratsiyalarda ko'rinadimi, degan savol.
const obj = {};
Object.defineProperty(obj, 'hidden', {
value: 'sir',
enumerable: false,
writable: true,
configurable: true
});
console.log(Object.keys(obj)); // []
console.log(JSON.stringify(obj)); // {}
console.log(obj.hidden); // 'sir' — to'g'ridan-to'g'ri o'qish ISHLAYDI!
Muhim nozik farq: enumerable: false faqat ko'rinishni yashiradi (iteration dan), lekin propertyga to'g'ridan-to'g'ri murojaat qilish hali ham ishlaydi. Bu "private" degani emas — bu faqat "ro'yxatda ko'rinmasin" degani.
Aynan shu mexanizm orqali built-in metodlar yashiringan:
console.log(Object.keys([1,2,3])); // ['0', '1', '2'] — 'length' ko'rinmaydi
length — enumerable: false, shuning uchun Object.keys uni ko'rsatmaydi, garchi u haqiqatan bor bo'lsa ham.
configurable
Bu eng "kuchli" flag. Ikki narsani nazorat qiladi:
- Property ni
deleteqilish mumkinmi - Property ning descriptorini (flag larni) qayta o'zgartirish mumkinmi
const obj = {};
Object.defineProperty(obj, 'x', {
value: 10,
configurable: false,
writable: true,
enumerable: true
});
delete obj.x; // false qaytadi, obj.x hali ham 10 (strict modeda TypeError)
Object.defineProperty(obj, 'x', { enumerable: false });
// ❌ TypeError: Cannot redefine property: x
Nozik istisno: configurable: false bo'lsa ham, agar writable: true bo'lsa, siz hali ham qiymatni obj.x = 20 orqali o'zgartira olasiz (chunki bu descriptor'ni qayta belgilash emas, oddiy yozish). Lekin writableni defineProperty orqali qayta o'zgartirmoqchi bo'lsangiz — bu bir tomonlama yo'l: writable: true → false ga o'tkazish mumkin, lekin writable: false → true ga qaytarish, configurable: false bo'lsa, mumkin emas.
Object.defineProperty(obj, 'y', {
value: 5, writable: true, configurable: false, enumerable: true
});
obj.y = 10; // ✅ ishlaydi — writable true
console.log(obj.y); // 10
Object.defineProperty(obj, 'y', { writable: false }); // ✅ ruxsat — true→false mumkin
Object.defineProperty(obj, 'y', { writable: true }); // ❌ TypeError — false→true configurable:false da mumkin emas
4. Oddiy obj.x = 10 bilan Object.defineProperty farqi
const obj = {};
obj.x = 10;
Bu yozganda JS engine avtomatik default descriptor qo'yadi:
{ value: 10, writable: true, enumerable: true, configurable: true }
Ya'ni — oddiy property yozish = "hammasiga ruxsat" bilan yaratish.
Object.defineProperty(obj, 'x', { value: 10 });
Bu esa default qiymatlar falsedan boshlanadi:
{ value: 10, writable: false, enumerable: false, configurable: false }
Bu eng ko'p uchraydigan xato manbasi: dasturchilar defineProperty ishlatib, keyin nega property for...in da ko'rinmayapti yoki nega o'zgarmayapti deb hayron bo'lishadi — sababi, ular writable/enumerableni aniq true qilib yozishni unutishadi.
5. Accessor Descriptor — getter/setter
const user = {
firstName: 'Ali',
lastName: 'Vali',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ');
}
};
console.log(user.fullName); // 'Ali Vali' — get chaqiriladi
user.fullName = 'Hasan Ahmad';
console.log(user.firstName); // 'Hasan'
V8 ichida bu qanday saqlanadi? fullName uchun Hidden Class da value yo'q, o'rniga get/set funksiyalariga pointer bor:
HiddenClass: {
fullName → AccessorDescriptor { getter: fn, setter: fn }
}
Har safar user.fullName o'qilganda, V8 to'g'ridan-to'g'ri qiymatni offsetdan olmaydi — u getter funksiyasini chaqiradi. Shuning uchun accessor property lar data property larga qaraganda sekinroq — chunki bu aslida funksiya chaqiruvi, oddiy memory access emas.
6. Object.freeze va Object.seal — descriptor larning amaliy qo'llanilishi
const obj = { x: 1, y: 2 };
Object.freeze(obj);
Object.freeze — barcha property larni writable: false, configurable: false qiladi (obyektning o'ziga yangi property qo'shish ham taqiqlanadi — extensible: false).
obj.x = 100; // sokin ignore (strict modeda TypeError)
obj.z = 5; // yangi property qo'sha olmaysiz
delete obj.x; // o'chira olmaysiz
console.log(obj); // { x: 1, y: 2 } — o'zgarmadi
Object.seal — faqat configurable: false qiladi (delete va yangi property qo'shish taqiqlanadi), lekin writable qoladi — ya'ni mavjud property larning qiymatini o'zgartirish mumkin:
const obj2 = { x: 1 };
Object.seal(obj2);
obj2.x = 99; // ✅ ishlaydi — writable hali true
obj2.y = 5; // ❌ yangi qo'sha olmaysiz
delete obj2.x; // ❌ o'chira olmaysiz
Farq bir gapda: freeze = "hech narsa o'zgarmasin" (qattiq immutable), seal = "struktura o'zgarmasin, lekin qiymatlar o'zgarishi mumkin".
⚠️ Muhim: ikkalasi ham shallow — faqat birinchi darajadagi property larga ta'sir qiladi:
const obj = Object.freeze({ nested: { a: 1 } });
obj.nested.a = 999; // ✅ ISHLAYDI! nested obyekt freeze qilinmagan
7. extensible nima?
Bu — biz ko'rgan writable, enumerable, configurable kabi property darajasidagi flag emas, balki butun obyektning o'ziga tegishli flag. Ya'ni u aynan bitta property haqida emas — "bu obyektga umuman yangi property qo'shsa bo'ladimi?" degan savolga javob beradi.
Har bir obyekt default holatda — extensible: true
const obj = { x: 1 };
console.log(Object.isExtensible(obj)); // true
obj.y = 2; // ✅ yangi property qo'shildi — muammosiz
console.log(obj); // { x: 1, y: 2 }
Oddiy holatda har qanday obyektga xohlagancha yangi property qo'shish mumkin — chunki u "extensible" (kengaytirilishi mumkin).
Object.preventExtensions() — kengaytirishni to'xtatish
const obj = { x: 1 };
Object.preventExtensions(obj);
console.log(Object.isExtensible(obj)); // false
obj.y = 2; // sokin ignore qilinadi (strict modeda TypeError)
console.log(obj); // { x: 1 } — 'y' qo'shilmadi!
Muhim: bu faqat yangi property qo'shishni taqiqlaydi. Mavjud property lar bilan hech narsa o'zgarmaydi:
obj.x = 100; // ✅ ishlaydi — x mavjud property, uni o'zgartirish mumkin (writable true bo'lsa)
delete obj.x; // ✅ ishlaydi — x ni o'chirish mumkin (configurable true bo'lsa)
Ya'ni preventExtensions — faqat "structure" ustidan nazorat: "yangi kirish taqiqlangan, lekin mavjudlar o'z holicha erkin".
Uch darajali "qattiqlik" tushunchasi
Endi to'liq tasavvur qilish uchun uchta metodni solishtiramiz — ular bir-biridan kuchliroq bo'lib boradi:
| Metod | Yangi property qo'shish | Mavjud property o'chirish | Mavjud property qiymatini o'zgartirish |
|---|---|---|---|
Object.preventExtensions() |
❌ | ✅ mumkin | ✅ mumkin |
Object.seal() |
❌ | ❌ | ✅ mumkin |
Object.freeze() |
❌ | ❌ | ❌ |
Object.seal() ichida aslida shu bo'ladi:
Object.preventExtensions(obj); // 1. kengaytirish taqiqlanadi
// + barcha mavjud property larga configurable: false qo'yiladi
Object.freeze() ichida esa:
Object.preventExtensions(obj); // 1. kengaytirish taqiqlanadi
// + configurable: false (seal kabi)
// + writable: false (bundan tashqari, qiymat ham qotib qoladi)
Ya'ni bu uchtasi — bir-birining ustiga qurilgan, bosqichma-bosqich kuchayib boruvchi cheklovlar:
preventExtensions → seal → freeze
(faqat yangi qo'shish taqiqlanadi) → (+ o'chirish taqiqlanadi) → (+ o'zgartirish ham taqiqlanadi)
8. V8 ichida nima bo'ladi?
Eslaysizmi, har bir obyekt Hidden Classga ega va yangi property qo'shilganda Transition Chain orqali yangi Hidden Classga o'tadi edi.
const obj = { x: 1 }; // HiddenClass: C1 {x}
Object.preventExtensions(obj);
obj.y = 2; // Transition qila olmaydi — chunki extensible: false
extensible: false qilingach, V8 uchun bu obyekt endi hech qachon yangi Hidden Class'ga o'tolmaydi — Transition Chain shu yerda "yopiladi". Bu ma'lum darajada V8'ga optimallashtirish imkoniyati ham beradi, chunki obyekt shakli endi hech qachon o'zgarmasligi kafolatlanadi.
9. Tekshirish uchun 3 ta metod
const obj = { x: 1 };
Object.isExtensible(obj); // true/false — kengaytirilishi mumkinmi?
Object.isSealed(obj); // true/false — seal qilinganmi? (extensible:false + barcha configurable:false)
Object.isFrozen(obj); // true/false — freeze qilinganmi? (seal + barcha writable:false)
Nozik nuqta: bo'sh obyektda ({}) Object.isSealed({}) va Object.isFrozen({}) — ikkalasi ham true qaytaradi, garchi hech narsa qilinmagan bo'lsa ham! Sababi — sealed/frozen bo'lish shartlari (hech qanday configurable/writable property yo'q) bo'sh obyekt uchun avtomatik bajariladi:
console.log(Object.isSealed({})); // true
console.log(Object.isFrozen({})); // true