Series: JavaScript Basics Lesson 39

Iterable Protocol

Protocol tushunchasi, Iterable Protocol'ning aniq spec ta'rifi, nega aynan Symbol.iterator ishlatilgani, GetIterator abstrakt operatsiyasi va Iterable bilan Iterator o'rtasidagi eng ko'p uchraydigan chalkashlik.

39-dars

Iterable Protocol

1. Protocol nima o'zi — umuman?

Avval "protocol" so'zini tushunib olaylik, chunki bu JS spec tilida juda muhim tushuncha. Protocol — bu klass emas, interfeys ham emas (TypeScript ma'nosida), balki oddiy "shartnoma": agar obyekt ma'lum bir shaklga (shape) mos kelsa, til mexanizmlari uni avtomatik tan oladi.

JS da rasmiy interface degan narsa yo'q. Buning o'rniga duck typing ishlaydi — "agar o'rdakdek yursa va o'rdakdek qichqirsa, demak bu o'rdak". Iterable Protocol aynan shu tamoyilga asoslangan: agar object'da to'g'ri joyda to'g'ri metod bo'lsa, u iterable hisoblanadi — qanday klass, qanday konstruktor ekani muhim emas.

ES6 spec ikkita bog'liq, lekin alohida protocol'ni kiritdi:

  • Iterable Protocol — "men iteratsiya qilinishim mumkin" deb e'lon qilish
  • Iterator Protocol — haqiqiy iteratsiya jarayonini yuritish

Biz oldingi mavzuda ikkalasini yuzaki ko'rgan edik. Endi faqat Iterable Protocolning o'ziga chuqur kiramiz — uning aniq talablari, spec darajasidagi qoidalari, va real dunyoda qanday ishlatilishi.


2. Iterable Protocol — aniq ta'rif

Spec bo'yicha (ECMA-262): Object Iterable hisoblanadi, agar unda [Symbol.iterator] kaliti ostida, argumentsiz chaqirilganda Iterator Result obyektlarini beruvchi Iterator qaytaradigan funksiya bo'lsa.

Formula ko'rinishida:

obj[Symbol.iterator]  // funksiya bo'lishi shart
obj[Symbol.iterator]() // chaqirilganda Iterator qaytarishi shart

Shu ikki shart bajarilsa — object rasman Iterable. Boshqa hech narsa talab qilinmaydi: name, class, inheritance — hech biri ahamiyatsiz.

// Eng minimal Iterable — hech qanday class kerak emas
const minimalIterable = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        return i < 3
          ? { value: i++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

[...minimalIterable]; // [0, 1, 2]
for (const x of minimalIterable) console.log(x); // 0 1 2

Bu object hech qanday class'dan kelmagan, oddiy {} literal, lekin til mexanizmlari uni to'liq tan oladi — chunki shartnoma bajarilgan.


3. Nega aynan Symbol.iterator — nega oddiy string emas?

Bu savol muhim, chunki bu Symbol'ning eng katta amaliy foydasini ko'rsatadi.

Agar ES6 dizaynerlari 'iterator' degan string key ishlatishga qaror qilsa edi:

// Hech qachon bunday bo'lmagan, lekin aytaylik shunday bo'lgan taqdirda
const arr = { iterator() { /* ... */ } };

Muammo: 'iterator' — oddiy string, va collision xavfi bor edi. Kimdir o'z object'ida allaqachon iterator nomli metod yozgan bo'lishi mumkin edi (masalan, matematik iteratsiya ma'nosida, JS'ning maxsus protokoliga aloqasiz). ES6 chiqqanda, millionlab mavjud kod birdan buzilib qolishi mumkin edi.

Symbol.iterator — bu well-known Symbol, ya'ni butun dunyoda noyob qiymat. Hech qanday mavjud kod tasodifan shu aniq Symbol'ni key sifatida ishlatib qo'ygan bo'lolmaydi (chunki Symbol'lar identity orqali ishlaydi, oldingi mavzuda ko'rganimizdek). Shu orqali ES6 eskilikni buzmasdan (backward compatibility) yangi til mexanizmini butun ekotizimga joriy qila oldi.

Symbol.iterator; // Symbol(Symbol.iterator) — bitta, global, o'zgarmas well-known symbol

Bu — Symbol'ning aynan "engine hook" sifatidagi vazifasi, oldingi mavzuda gaplashgan edik.


4. Qaysi built-in turlar Iterable — va qaysilari emas

Bu jadval muhim, chunki ko'p dasturchilar noto'g'ri taxmin qiladi:

Tur Iterable'mi? Izoh
Array ✅ [Symbol.iterator] prototype'da bor
String ✅ Har bir code point (emoji, surrogate pair to'g'ri hisoblanadi) bo'yicha yuradi
Map ✅ [key, value] juftliklari bo'yicha
Set ✅ Qiymatlar bo'yicha
NodeList (DOM) ✅ Zamonaviy brauzerlarda
arguments obyekti ✅ Array-like, lekin Array emas — shunga qaramay iterable
oddiy Object ({}) ❌ Symbol.iterator yo'q
Number, Boolean ❌ Ma'nosi yo'q — nima ustidan yurish kerak?

Stringning iterable ekanligi qiziqarli tafsilotni ochadi:

const str = 'a😀b';

str.length; // 4 — chunki 😀 ikkita UTF-16 code unit (surrogate pair)

[...str]; // ['a', '😀', 'b'] — 3 ta, to'g'ri!
for (const ch of str) console.log(ch); // 'a' '😀' 'b'

// Lekin klassik indexlash orqali:
str[1]; // buzilgan surrogate yarmi — noto'g'ri

Stringning Symbol.iterator implementatsiyasi ichida Unicode code pointlarni to'g'ri hisoblab, surrogate pair'larni birlashtirib beradi — bu oddiy .length/indexlash bilan qilib bo'lmaydigan narsa. Bu Iterable Protocol'ning nega faqat "qulaylik" emas, balki to'g'rilik uchun ham kerakligini ko'rsatadi.


5. Iterable'ni ishlatuvchi til mexanizmlari

Bu — protokolning kuchini ko'rsatadigan eng muhim qism. Bitta Symbol.iterator implementatsiya qilinsa, for...of, spread (...), destructuring va Array.from() kabi mexanizmlarning hammasi avtomatik ishlay boshlaydi:

class Range {
  constructor(start, end) { this.start = start; this.end = end; }
  [Symbol.iterator]() {
    let cur = this.start, end = this.end;
    return { next: () => cur <= end
      ? { value: cur++, done: false }
      : { value: undefined, done: true } };
  }
}

const r = new Range(1, 3);

for (const n of r) { } // 1, 2, 3

6. Iterable Protocol V8 ichida qanday tekshiriladi (spec algoritmi)

Spec darajasida for...of (va boshqa iterable-consumer mexanizmlar) quyidagi GetIterator abstrakt operatsiyasini bajaradi:

GetIterator(obj):
  1. method = obj[Symbol.iterator]   // property lookup
  2. Agar method funksiya bo'lmasa → TypeError tashla
  3. iterator = method.call(obj)     // chaqirish
  4. Agar iterator object bo'lmasa → TypeError tashla
  5. iterator ni qaytar

Amalda buni ko'rish mumkin:

const notIterable = { [Symbol.iterator]: 'men funksiya emasman' };

for (const x of notIterable) { }
// TypeError: notIterable is not iterable
// chunki [Symbol.iterator] funksiya emas, spec 2-qadamda tekshiriladi
const badIterable = { [Symbol.iterator]() { return 42; } }; // primitive qaytardi

for (const x of badIterable) { }
// TypeError: Result of the Symbol.iterator method is not an object
// spec 4-qadamda tekshiriladi

V8 ichida bu tekshiruv har safar for...of yoki spread ishlatilganda amalga oshiriladi — lekin built-in turlar (Array, String va h.k.) uchun V8 maxsus internal flag/shape orqali buni chetlab o'tadi (fast path), chunki V8 o'zining ichki Array implementatsiyasi Symbol.iteratorni o'zgartirmaganini oldindan biladi (agar dasturchi prototype'ni buzmagan bo'lsa).


7. Iterable — bu Iterator emas! (eng ko'p uchraydigan xato)

Bu farqni chuqurroq mustahkamlash kerak, chunki bu joyda ko'pchilik adashadi:

const arr = [1, 2, 3];

for (const x of arr) { }        // ✅ ishlaydi — arr Iterable
const it = arr[Symbol.iterator]();
for (const x of it) { }         // ✅ ham ishlaydi — nega?

Ikkinchisi ham ishlaydi, chunki standart Iterator'lar odatda o'zlari ham Iterable bo'ladi — ular o'zlarining [Symbol.iterator]() metodiga ega, va u shunchaki thisni qaytaradi:

it[Symbol.iterator] === function() { return this; }; // aynan shunday

Bu — "self-iterable" naqshi, deb ataladi. Aynan shu sabab for (const x of it) {} bittagina marta to'liq ishlab, tugagach yana ishlatilsa bo'sh qaytadi (chunki it allaqachon "tugagan" holatda) — Iterator holat saqlaydi (stateful), Iterable esa (odatda) yangi Iterator yaratib beradi (stateless bo'lishi kerak, lekin bu qat'iy talab emas, faqat konvensiya).

Muhim xulosa jadvali:

Iterable Iterator
Talab qilingan metod [Symbol.iterator]() .next()
Vazifasi Iterator "ishlab chiqaradi" Iteratsiya jarayonini yuritadi
Holat (state) saqlaydimi? Yo'q (odatda) Ha (kursor pozitsiyasi)
Qayta ishlatsa bo'ladimi? Ha, cheksiz marta yangi Iterator beradi Yo'q, bir marta "tugaydi"

8. Well-formed Iterable — amaliy qoida (best practice)

Yaxshi dizayn qilingan Iterable har safar [Symbol.iterator]() chaqirilganda yangi, mustaqil Iterator qaytarishi kerak — bu orqali bir obyektni bir nechta joyda parallel iteratsiya qilish mumkin bo'ladi:

// ✅ To'g'ri — har chaqiruv yangi holat
class Range {
  [Symbol.iterator]() {
    let cur = this.start;
    return { next: () => /* yangi cur bilan */ };
  }
}

// ❌ Noto'g'ri — bitta umumiy holatni bo'lishadi
class BadRange {
  #cur = this.start;
  [Symbol.iterator]() {
    return { next: () => /* this.#cur ni ishlatadi — hamma chaqiruv bitta holatni buzadi */ };
  }
}

Ikkinchi holatda, agar ikkita for...of bir vaqtda (yoki ketma-ket, tugamasdan) ishlatilsa, ular bir-birining kursorini buzadi — bu odatiy bug manbai.


Xulosa

Iterable Protocol — JS'da eng ko'p ishlatiluvchi, lekin eng kam ko'rinadigan mexanizmlardan biri: bu klass yoki interfeys emas, oddiy shartnoma — [Symbol.iterator] funksiya bo'lishi va chaqirilganda .next() metodiga ega Iterator qaytarishi kerak, xolos. Aynan Symbol.iterator (oddiy string emas) ishlatilgani ES6'ga eskilikni buzmasdan yangi mexanizmni joriy qilish imkonini berdi, chunki well-known Symbol collision'dan xoli. Spec darajasida bu shartnoma GetIterator abstrakt operatsiyasi orqali tekshiriladi, V8 esa built-in turlar uchun bu tekshiruvni fast path bilan chetlab o'tadi. Iterable va Iterator — ikki alohida rol: Iterable (odatda stateless) har chaqiruvda yangi Iterator "ishlab chiqaradi", Iterator esa (stateful) haqiqiy kursor pozitsiyasini yuritadi va bir marta tugaydi — bu farqni bilish "well-formed" Iterable yozish va parallel iteratsiyalarni bir-birini buzmasdan ishlatish uchun zarur.

                    Symbol.iterator (shartnoma)
                            │
        ┌───────────┬───────┴───────┬───────────┐
        ▼           ▼               ▼           ▼
    for...of    spread (...)   destructuring   Array.from()