Symbol
Symbol nima, nega yaratilgan, V8 uni xotirada qanday saqlaydi, Hidden Class bilan bog'liqligi, well-known Symbol'lar (Symbol.iterator, Symbol.toPrimitive) va Symbol.for() global registry.
37-dars
Symbol
1. Symbol nima va nega kerak?
ES6 gacha object property key faqat ikki xil bo'lishi mumkin edi — string yoki (avtomatik string ga aylantiriladigan) hech nima. Bu bir muammo tug'dirardi: agar siz kutubxona yozayotgan bo'lsangiz va object ga "yashirin" metadata qo'ymoqchi bo'lsangiz, qanday key ishlatasiz? Qanday nom tanlasangiz ham, foydalanuvchi undan bexabar xuddi shu nomdagi property yozib qo'yishi mumkin — name collision.
// Kutubxona yozuvchisi:
obj.__id__ = 'secret-123';
// Foydalanuvchi bilmagan holda:
obj.__id__ = 'boshqa narsa'; // konflikt!
Symbol shu muammoni hal qilish uchun yaratildi: har doim noyob (unique) qiymat yaratadigan yangi primitive tur.
const s1 = Symbol('id');
const s2 = Symbol('id');
s1 === s2; // false — description bir xil bo'lsa ham!
'id' — bu faqat description (debugging uchun), qiymatga ta'sir qilmaydi. Har Symbol() chaqirilganda butunlay yangi, boshqa hech narsaga teng bo'lmagan qiymat yaratiladi.
Symbol — bu primitive type (undefined, null, boolean, number, string, bigint, symbol). Lekin boshqa primitivlardan farqi shunda — u qiymat orqali emas, identity (o'ziga xoslik) orqali taqqoslanadi.
2. V8 xotirada Symbol'ni qanday ifodalaydi
V8 xotirada Symbol'ni quyidagicha ifodalaydi: har bir Symbol — bu heap dagi noyob obyekt-manzilga ega bo'lgan yorliq. Symbol('id') chaqirilganda V8:
- Yangi ichki Symbol obyekt yaratadi (unique identity bilan)
descriptionmaydonini'id'qiladi (faqat metama'lumot)- Bu obyektga pointer qaytaradi
Symbol('id') → heap: [Symbol#0x1A2B { description: 'id' }]
Symbol('id') → heap: [Symbol#0x3F9C { description: 'id' }]
Ikkalasi ham description: 'id', lekin xotiradagi manzillari boshqa — shuning uchun === false qaytaradi.
3. Symbol property qanday saqlanadi (Hidden Class bilan bog'liqlik)
Oldin ko'rgan Hidden Class mexanizmini eslang — object property lar offset orqali saqlanadi. Symbol key lar ham xuddi shunday property sifatida saqlanadi, lekin V8 ularni alohida internal tableda tutadi — chunki Symbol key lar for...in, Object.keys(), JSON.stringify() da ko'rinmasligi kerak.
const idSymbol = Symbol('id');
const user = {
name: 'Ali',
[idSymbol]: 12345
};
Object.keys(user); // ['name'] — symbol yo'q
JSON.stringify(user); // '{"name":"Ali"}' — symbol yo'q
for (let k in user) { } // faqat 'name' — symbol yo'q
Object.getOwnPropertySymbols(user); // [Symbol(id)] — faqat shu orqali ko'rinadi
Reflect.ownKeys(user); // ['name', Symbol(id)] — hammasi
V8 ichida object ikkita alohida property storage ga ega bo'ladi: string-keyed properties (Hidden Class transition chain orqali) va symbol-keyed properties (alohida yashiringan qism). Shuning uchun oddiy iteratsiya (for...in, Object.keys) ularni ko'rmaydi — ular enumerable emas dizayn bo'yicha, lekin private ham emas (getOwnPropertySymbols orqali baribir olsa bo'ladi).
Muhim: Symbol — bu haqiqiy private emas.
#privateField(class ichida) dan farqli, Symbol'ga refleksiya orqali yetib bo'ladi. Symbol maqsadi — collision oldini olish, security emas.
4. Well-known Symbols — JS engine "hook" lari
Bu qism eng muhim qism: Symbol faqat sizning kodingiz uchun emas, JS engine'ning o'zi foydalanadigan maxsus Symbol'lar bor. Ular Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance va hokazo.
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
// Bu — engine "for...of qachon ishlatilsa shu funksiyani chaqir" degan hook
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
}
for (const num of new Range(1, 5)) {
console.log(num); // 1 2 3 4 5
}
for...of ichida V8 quyidagini qiladi: object'da Symbol.iterator bormi tekshiradi → bo'lsa, chaqiradi → qaytgan obyektning .next() metodini chaqiraverib, done: true bo'lguncha davom etadi.
Boshqa muhim well-known symbol'lar:
| Symbol | Vazifasi |
|---|---|
Symbol.iterator |
for...of, spread (...) qanday iteratsiya qilishni belgilaydi |
Symbol.toPrimitive |
obj + 1, `${obj}` kabi holatlarda obyekt qanday primitive'ga aylanishini belgilaydi |
Symbol.toStringTag |
Object.prototype.toString.call(obj) natijasini o'zgartiradi |
Symbol.hasInstance |
instanceof operatorining xatti-harakatini custom qiladi |
Symbol.asyncIterator |
for await...of uchun |
class Money {
constructor(amount) { this.amount = amount; }
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount;
if (hint === 'string') return `$${this.amount}`;
return `Money(${this.amount})`;
}
}
const m = new Money(100);
console.log(+m); // 100 (hint: 'number')
console.log(`${m}`); // '$100' (hint: 'string')
console.log(m + ''); // 'Money(100)' (hint: 'default')
Bu — nega ba'zi built-in obyektlar (Date, masalan) + yoki template string ichida "aqlli" ishlaydi, degan savolga javob. Ular ichida aynan shu Symbol.toPrimitive (yoki eski valueOf/toString) implementatsiya qilingan.
5. Symbol.for() — global registry
Oddiy Symbol() har doim yangi, noyob qiymat yaratadi. Lekin ba'zan sizga turli fayllar/modullar orasida bir xil Symbolni bo'lishish kerak bo'ladi. Shu uchun Symbol.for() bor:
const s1 = Symbol.for('app.id');
const s2 = Symbol.for('app.id');
s1 === s2; // true!
V8 buni global Symbol registry (engine darajasidagi bitta global Map<string, Symbol>) orqali amalga oshiradi:
Global Symbol Registry:
'app.id' → Symbol#0x7C1D
Symbol.for('app.id') chaqirilganda: registry'da 'app.id' bormi tekshiriladi → bo'lsa, o'sha mavjud Symbol qaytariladi → bo'lmasa, yangi yaratiladi va registry'ga yoziladi. Bu registry butun JS runtime (hatto turli iframe/realm lar) bo'ylab umumiy, chunki u V8 process darajasida saqlanadi, oddiy module scope emas.
Symbol.keyFor(s1) — teskarisini qiladi, Symbol'dan registry key'ni topadi.
6. Symbol vs boshqa yechimlar — nega ayni shu kerak?
| Yechim | Muammosi |
|---|---|
'_privateName' string key |
Collision xavfi, hech qanday himoya yo'q |
WeakMap orqali private data |
Ishlaydi, lekin og'ir syntax, ko'p object uchun noqulay |
#privateField (ES2022) |
Haqiqiy private, lekin faqat class ichida ishlaydi, dinamik key bo'la olmaydi |
Symbol |
Collision'siz key, engine hook'lari uchun standart, lekin private emas |
Xulosa
Symbol'ning asosiy o'rni — "metaprogramming key": object'ning "oddiy data"si bilan "maxsus xulq-atvor"ini key darajasida ajratish. Har bir Symbol() chaqiruvi heap'da noyob identity yaratadi, shuning uchun bir xil description'ga qaramay === doim false bo'ladi; V8 bu key'larni alohida internal tableda tutadi, shuning uchun ular for...in, Object.keys() va JSON.stringify() da ko'rinmaydi, lekin Object.getOwnPropertySymbols() orqali baribir olsa bo'ladi — ya'ni collision'dan himoyalaydi, ammo private emas. Well-known Symbol'lar (Symbol.iterator, Symbol.toPrimitive va h.k.) esa engine'ning o'zi tan oladigan "hook" lar bo'lib, for...of yoki + kabi operatorlarning custom obyektlar bilan qanday ishlashini belgilaydi, Symbol.for() esa global registry orqali modullar o'rtasida bitta Symbolni bo'lishish imkonini beradi.