IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur51/65-dars23 daqiqa
Mundarija (37)

JavaScript'da o'z iterable obyektingizni yasash: Symbol.iterator qo'lda

Qisqacha: Obyektni for...of bilan aylanadigan qilish uchun unga [Symbol.iterator]() metodini qo'shing. Bu metod har chaqirilganda yangi iterator obyektini qaytarsin: unda next() bo'lsin va u { value, done } bersin. Iterator holati (qayerdaman?) closure'da tursin — shunda obyektni xohlagancha qayta aylanish mumkin. Erta to'xtashda tozalash kerak bo'lsa — return() metodi.

Bu darsda

  • Oddiy obyektga [Symbol.iterator]() yozib, uni for...of, spread va destructuring bilan ishlatasiz.
  • oraliq(boshi, oxiri, qadam) fabrikasini — JavaScript'da yo'q range ni — o'zingiz yasaysiz.
  • Qayta ishlatiladigan va bir martalik iterable farqini tushuntira olasiz.
  • Klassga [Symbol.iterator]() qo'shib, ichki massivni yashirgan holda aylanishga ruxsat berasiz.
  • return() metodi qachon chaqirilishini bilasiz va u bilan tozalash ishini yozasiz.
  • vazifalar dagi VazifalarRoyxati ni iterable qilasiz.

Oldin bilishingiz kerak: Iteratsiya protokoli, Closure: funksiya o'z muhitini eslab qoladi, this: to'rt qoida va arrow istisnosi, Private # maydon va metodlar, OOP tamoyillari va kompozitsiya.

1. Nega bu kerak?

«Bahor» bronni soat 10:00 dan 22:00 gacha qabul qiladi. Bron formasida soatlar ro'yxati kerak, ish vaqti panelida — boshqa ro'yxat, statistikada — yana bittasi. Har safar bir xil sikl yoziladi:

js
const soatlar = [];
for (let soat = 10; soat < 22; soat++) {
  soatlar.push(soat);
}
console.log(soatlar.length); // 12

Python kabi ba'zi tillarda buning uchun tayyor range(10, 22) bor. JavaScript'da yo'q. Lekin o'tgan darsda bilib oldik: for...of "massiv bormi?" deb so'ramaydi, faqat "Symbol.iterator bormi?" deb so'raydi. Demak protokolga amal qiladigan o'z obyektimizni yasasak, u massiv bilan teng huquqli bo'ladi: for...of, spread, destructuring, Array.from — hammasi ishlaydi.

O'tgan darsdagi o'xshatishni davom ettiramiz. Hozirgacha tayyor kitoblarni (massiv, satr, Map) o'qidik. Bugun kitobni ham, unga xatcho'pni ham o'zimiz yasaymiz.

2. Birinchi iterable: bron soatlari

2.1 Kod

js
const bronSoatlari = {
  boshi: 10,
  oxiri: 13,

  [Symbol.iterator]() {
    let joriy = this.boshi;
    const oxiri = this.oxiri;
    return {
      next() {
        if (joriy < oxiri) {
          return { value: joriy++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

for (const soat of bronSoatlari) {
  console.log(`${soat}:00`);
}

Konsolda:

text
10:00
11:00
12:00

(Konsol qisqa bo'lishi uchun oxiri — 13. Haqiqiy bron uchun 22 yozasiz.)

2.2 Ikki qavat

Kodda ikki obyekt bor — ularni ajratib o'qing:

  1. Tashqi obyekt — iterable. Unda ma'lumot (boshi, oxiri) va bitta maxsus metod — [Symbol.iterator](). Metod nomi kvadrat qavsda, chunki kalit — symbol (Computed kalitlar).
  2. Ichki obyekt — iterator. [Symbol.iterator]() uni har chaqiruvda yangidan yasab qaytaradi. Unda faqat next().

next() qayerda turganini qayerdan biladi? joriy — [Symbol.iterator]() chaqiruvining lokal o'zgaruvchisi. next() shu muhitda tug'ilgan, demak uni "orqa cho'ntagida" olib yuradi — bu closure. Har next() joriy ni o'qiydi va joriy++ bilan bittaga suradi.

joriy++ — avval eski qiymatni beradi, keyin oshiradi. Shuning uchun birinchi value — 10, joriy esa 11 bo'lib qoladi.

this.boshi faqat bir marta, [Symbol.iterator]() ichida o'qiladi. U yerda this — bronSoatlari, chunki metod bronSoatlari[Symbol.iterator]() shaklida chaqiriladi (this qoidalari). next() ichida esa this ishlatmadik — sababini «Ko'p uchraydigan xatolar» bo'limida ko'ramiz.

2.3 Qadamma-qadam

Keyingi ni bosing: destructuring qachon [Symbol.iterator]() ni chaqiradi va joriy closure ichida qanday o'zgaradi?

2.4 Endi u massiv bilan teng huquqli

Protokolni bilgan har bir joy bizning obyektni ham tushunadi:

js
console.log([...bronSoatlari]); // [ 10, 11, 12 ]
console.log(Math.max(...bronSoatlari)); // 12
const yozuvlar = Array.from(bronSoatlari, (s) => `${s}:00`);
console.log(yozuvlar); // [ '10:00', '11:00', '12:00' ]

Array.from ning ikkinchi argumenti — har qiymatni o'zgartiruvchi funksiya (Array.from). Biz hech qayerda massiv yozmadik — qiymatlar so'ralgan paytda next() ichida tug'ildi.

Tekshirib ko'ring: bronSoatlari.oxiri = 11; deb yozib, keyin [...bronSoatlari] qilsak, nima chiqadi? Nega?

Javob

[ 10 ]. oxiri har yangi aylanish boshida, [Symbol.iterator]() ichida o'qiladi. Spread yangi iterator so'radi — u yangilangan oxiri ni oldi. Ammo allaqachon boshlangan iteratorga bu ta'sir qilmaydi: u oxiri ni o'z muhitiga ko'chirib bo'lgan.

3. Fabrika: oraliq(boshi, oxiri, qadam)

3.1 Har safar obyekt yozmaslik uchun

Bitta oraliq uchun obyekt yozish mumkin. Lekin bron soatlari, stol raqamlari, juft stollar — har biriga qo'lda obyekt yozish takror ish. Closure amaliyotda darsidagi fabrika funksiya shu yerda kerak: sozlamani bir marta oladi, tayyor iterable qaytaradi.

js
function oraliq(boshi, oxiri, qadam = 1) {
  return {
    [Symbol.iterator]() {
      let joriy = boshi;
      return {
        next() {
          if (qadam > 0 && joriy < oxiri) {
            const qiymat = joriy;
            joriy += qadam;
            return { value: qiymat, done: false };
          }
          return { value: undefined, done: true };
        },
      };
    },
  };
}

console.log([...oraliq(10, 22)].length); // 12
console.log([...oraliq(1, 6)]); // [ 1, 2, 3, 4, 5 ]
console.log([...oraliq(2, 11, 2)]); // [ 2, 4, 6, 8, 10 ]
console.log([...oraliq(5, 5)]); // []

Endi this umuman kerak emas: boshi, oxiri, qadam — fabrika parametrlari, ular closure'da. oraliq(10, 22) — o'n ikki bron soati, oraliq(2, 11, 2) — juft raqamli stollar.

O'zingiz to'ldiring. [...oraliq(3, 6)] natijasi — [3, 4, ]. [...oraliq(0, 10, 5)] natijasi — [0, ], unda ta son bor.

3.2 qadam > 0 sharti nega bor?

Shart joriy < oxiri ning o'zi bo'lsa, oraliq(1, 5, 0) nima qiladi? joriy hech qachon o'smaydi, 1 < 5 doim rost — iterator cheksiz 1 beradi. Spread esa done: true ni abadiy kutadi va dastur qotadi. qadam > 0 && himoya: noto'g'ri qadam bilan iterable shunchaki bo'sh bo'ladi.

Xato tashlab, "qadam musbat bo'lishi kerak" deyish yanada yaxshi bo'lardi. Buni throw va Error obyekti darsida o'rganamiz — hozir bilish shart emas.

Tekshirib ko'ring: [...oraliq(10, 12, 0.5)] nima beradi?

Javob

[ 10, 10.5, 11, 11.5 ] — har yarim soat. 12 kirmaydi: shart joriy < oxiri, ya'ni oxiri qo'shilmaydi. Massivdagi slice(1, 3) kabi — boshi kiradi, oxiri kirmaydi. Bu kelishuv tufayli oraliq(0, n) aynan n ta qiymat beradi.

3.3 Massiv emas — so'ralganda tayyorlanadi

oraliq(1, 1000001) million son beradi. Lekin xotirada million son yo'q: obyektda faqat uchta parametr (closure'da) va bitta metod bor. Sonlar next() so'ralgan paytda bittadan tug'iladi va ishlatib bo'lingach unutiladi. for (let i = 1; i <= 1000000; i++) ham xuddi shunday tejamkor, lekin oraliq ni o'zgaruvchiga saqlash, funksiyaga berish, spread qilish mumkin — u qiymat.

Massiv esa hamma sonlarni oldindan, bir vaqtda saqlaydi. Million element — million katak. Kichik ro'yxatlarda farq sezilmaydi. Katta yoki cheksiz ketma-ketlikda esa iterable — yagona yo'l.

Shuning uchun oraliq ni tayyor non to'la savat emas, tandirchi deb tasavvur qiling. Savatdagi non oldindan yopilgan va joy egallaydi. Tandirchi esa "so'rasangiz, keyingisini yopib beraman" deydi. O'tgan darsdagi navbat chiptalari apparati aynan shunday edi.

4. Qayta ishlatiladigan va bir martalik

4.1 Holat qayerda turadi — hal qiluvchi savol

Bizning oraliq ni ikki marta spread qilib ko'ramiz:

js
const stollar = oraliq(1, 4);
console.log([...stollar]); // [ 1, 2, 3 ]
console.log([...stollar]); // [ 1, 2, 3 ]

Ikkinchi marta ham to'liq. Chunki har spread [Symbol.iterator]() ni chaqiradi va u yangi joriy bilan yangi iterator yasaydi. Kitob bitta — xatcho'plar har safar yangi.

Endi holatni obyektning o'ziga qo'yamiz va obyekt o'zini iterator sifatida qaytarsin:

js
const navbat = {
  joriy: 1,
  oxiri: 4,
  [Symbol.iterator]() {
    return this;
  },
  next() {
    if (this.joriy < this.oxiri) {
      return { value: this.joriy++, done: false };
    }
    return { value: undefined, done: true };
  },
};

console.log([...navbat]); // [ 1, 2, 3 ]
console.log([...navbat]); // []

Ikkinchi spread bo'sh. navbat — kitob ham, xatcho'p ham. Birinchi o'qishda xatcho'p oxiriga borib qoldi, ikkinchi o'qish o'sha joydan boshlandi. Bu — bir martalik iterable. Massiv iteratori (taomlar.values()) ham aynan shunday ishlaydi (Iteratsiya protokoli darsidagi "iterator bir marta o'qiladi" qoidasi).

Bu yerda next() ichida this to'g'ri ishlaydi: next navbat ning metodi va u navbat.next() shaklida chaqiriladi.

Ikki dizaynni yonma-yon solishtiring:

flowchart LR
  subgraph Q["Qayta ishlatiladigan"]
    A1["oraliq(1, 4)"] -->|"1-spread"| B1["yangi<br/>joriy=1"]
    A1 -->|"2-spread"| C1["yangi<br/>joriy=1"]
  end
  subgraph B["Bir martalik"]
    A2["navbat<br/>(joriy obyektda)"] -->|"1-spread"| A2
    A2 -->|"2-spread: joriy = 4"| D2["bo'sh"]
  end

4.2 Qaysi biri qachon?

Ma'lumot Dizayn Misol
Qayta o'qiladigan to'plam qayta ishlatiladigan menyu, oraliq, ro'yxat
Faqat bir marta keladigan oqim bir martalik navbat chiptalari, tarmoqdan kelayotgan javob

Qoida: to'plam yasayotgan bo'lsangiz — holat [Symbol.iterator]() ichida, closure'da. Bir martalik dizaynni faqat ma'lumotning tabiati shunday bo'lsa tanlang. Masalan, tarmoqdan kelayotgan javobni ikki marta "qaytadan o'qib" bo'lmaydi.

4.3 Iteratorni ham iterable qiling

Bizning bronSoatlari iteratorini qo'lda olib, for...of ga bersak-chi? Quyida next() qisqaroq yozilgan: value ga avval joriy yoziladi. Keyin joriy++ >= 13 eski qiymatni 13 bilan solishtiradi va shundan keyingina joriy ni oshiradi. Ma'nosi oldingi kod bilan bir xil.

js
const bronSoatlari = {
  [Symbol.iterator]() {
    let joriy = 10;
    return {
      next() {
        return { value: joriy, done: joriy++ >= 13 };
      },
    };
  },
};

const iterator = bronSoatlari[Symbol.iterator]();
iterator.next(); // 10 ni o'tkazib yuboramiz
for (const soat of iterator) {
  console.log(soat);
}

Konsolda:

text
TypeError: iterator is not iterable

Tarjimasi: "iterator — iterable emas". for...of iteratordan ham [Symbol.iterator]() ni so'raydi, bizning iteratorda esa u yo'q. O'rnatilgan iteratorlar bu muammoni bilmaydi: ular o'zini qaytaradigan [Symbol.iterator]() ga ega. O'zimiznikiga ham shuni qo'shamiz:

js
return {
  next() { ... },
  [Symbol.iterator]() {
    return this;
  },
};

Shunda iteratorni yarim yo'lda olib, qolganini for...of ga berish mumkin. Bu yaxshi odat — o'z iteratorlaringizga doim qo'shing.

Tekshirib ko'ring: navbat obyektida [Symbol.iterator]() { return this; } ni olib tashlasak, [...navbat] nima qiladi?

Javob

TypeError: navbat is not iterable. Obyektda next() bor — u iterator. Lekin for...of va spread iterator emas, iterable kutadi: avval [Symbol.iterator]() ni chaqiradi. U yo'q — xato. next() ning borligi yetarli emas.

4.4 O'z iterable'ingiz uchun tekshiruv ro'yxati

Yangi iterable yozib bo'lgach, besh savolga javob bering:

  1. Metod nomi [Symbol.iterator] — kvadrat qavsda, symbol bilanmi?
  2. U har chaqiruvda yangi iterator qaytaradimi (holat closure'da)?
  3. next() har doim { value, done } obyektini qaytaradimi?
  4. Qiymatlar tugaganda done: true albatta keladimi? (Cheksiz bo'lishi ataylab bo'lsa — buni nomida yoki izohda ayting.)
  5. Iteratorda o'zini qaytaradigan [Symbol.iterator]() bormi?

Beshtasiga "ha" bo'lsa — obyektingiz o'rnatilgan massiv iteratori bilan bir xil "odob" bilan ishlaydi. Tozalash kerak bo'lgan iteratorlar uchun oltinchi savol bor — uni «return() — erta to'xtashda tozalash» bo'limida ko'ramiz.

5. Klassda iterable

5.1 Metod nomi — kvadrat qavsda

«Bahor» menyusini klass qilib yozamiz. Taomlar private massivda (Private maydonlar) — tashqi kod ularni o'zgartira olmasin. Lekin aylanib chiqishga ruxsat beramiz:

js
class Menyu {
  #taomlar = [];

  qosh(nom, narx) {
    this.#taomlar.push({ nom, narx });
  }

  [Symbol.iterator]() {
    const taomlar = this.#taomlar;
    let i = 0;
    return {
      next() {
        if (i < taomlar.length) {
          return { value: taomlar[i++], done: false };
        }
        return { value: undefined, done: true };
      },
      [Symbol.iterator]() {
        return this;
      },
    };
  }
}

const menyu = new Menyu();
menyu.qosh("Osh", 35000);
menyu.qosh("Manti", 30000);

for (const taom of menyu) {
  console.log(`${taom.nom} — ${taom.narx} so'm`);
}
console.log(menyu);

Konsolda:

text
Osh — 35000 so'm
Manti — 30000 so'm
Menyu {}

Klass tanasida metod nomi obyektdagidek yoziladi: [Symbol.iterator]() { ... }. Metod prototipga tushadi — har menyu nusxasi uni meros oladi (class ichidan). Konsolda Menyu {} — taomlar private, ko'rinmaydi. Ammo for...of ularni bemalol aylandi.

5.2 Nega massivni o'zini bermaymiz?

Oddiyroq yo'l bordek: get taomlar() { return this.#taomlar; }. Lekin u private massivning o'zini qaytaradi. Tashqi kod unga push qilsa — klass qoidalarini chetlab o'tadi (Closure amaliyotda darsidagi "ichki obyektni tashqariga bermang" qoidasi).

Iterator esa faqat bitta eshik ochadi — "keyingisini ber". Unda push ham, length = 0 ham yo'q. Tashqi kod [...menyu] bilan nusxa olishi mumkin, lekin nusxani buzsa, menyu butun qoladi.

5.3 Qisqa yo'l: topshirish

Ichki ma'lumot massiv bo'lsa, iteratorni qo'lda yozish shart emas. Massivning o'z iteratorini qaytarish kifoya:

js
[Symbol.iterator]() {
  return this.#taomlar[Symbol.iterator]();
}

Bitta qator — va massiv iteratorining hamma xususiyatlari bepul keladi: o'zini qaytaradigan [Symbol.iterator]() ham, "jonli" ko'rinish ham (aylanish paytida massivga qo'shilgan element ham chiqadi). Bu — OOP tamoyillari darsidagi kompozitsiya: ishni ichki obyektga topshirish. Qo'lda yozilgan variantni bugun tushunish uchun ko'rdik. Real kodda ichida massiv bo'lsa — topshirish yo'li.

Tekshirib ko'ring: Menyu klassida iteratorni qo'lda yozganda const taomlar = this.#taomlar; qatorini olib tashlab, next() ichida to'g'ridan-to'g'ri this.#taomlar[i++] deb yozsak, nima bo'ladi?

Javob

next() iterator obyektining metodi — uning ichida this menyu emas, iterator. Iteratorda esa #taomlar private maydoni yo'q. Private maydonga "begona" obyekt orqali murojaat qilish jim o'tmaydi — TypeError chiqadi: "Cannot read private member #taomlar from an object whose class did not declare it" ("bu obyektning klassi #taomlar ni e'lon qilmagan"). Shuning uchun massivni [Symbol.iterator]() ichida (u yerda this — menyu) closure o'zgaruvchisiga olamiz.

6. return() — erta to'xtashda tozalash

6.1 Iterator "tugatilmay" qolsa

Ba'zi iteratorlar ish boshida biror narsani ochadi: faylni, ulanishni, hodisa tinglovchisini. Oxirida uni yopish kerak. Iterator oxiriga yetsa — yopish oson: done: true qaytarishdan oldin yopadi. Lekin for...of dagi break iteratorni yarim yo'lda tashlab ketadi. Destructuring ham kerakligicha olib, qolganini tashlaydi.

Shu holat uchun protokolda ixtiyoriy metod bor — return(). Iterator oxiriga yetmay to'xtatilsa, JavaScript uni o'zi chaqiradi.

Nomiga e'tibor bering: return() — iterator obyektidagi metod nomi. Funksiyadan qiymat qaytaradigan return kalit so'zi bilan bir xil yoziladi, lekin boshqa narsa. Quyidagi kodda ikkalasi yonma-yon keladi: return() { ... return { ... }; } — birinchisi metod nomi, ikkinchisi oddiy return.

«Bahor» kassasi misolida:

js
function kassaNavbati(buyurtmalar) {
  return {
    [Symbol.iterator]() {
      console.log("Kassa ochildi");
      let i = 0;
      return {
        next() {
          if (i < buyurtmalar.length) {
            return { value: buyurtmalar[i++], done: false };
          }
          console.log("Kassa yopildi: navbat tugadi");
          return { value: undefined, done: true };
        },
        return() {
          console.log("Kassa yopildi: erta to'xtatildi");
          return { value: undefined, done: true };
        },
      };
    },
  };
}

const navbat = kassaNavbati(["osh", "manti", "choy"]);

for (const b of navbat) {
  console.log(b);
  if (b === "manti") break;
}
console.log("---");
const [birinchi] = navbat;
console.log("---");
console.log([...navbat].length);

Konsolda:

text
Kassa ochildi
osh
manti
Kassa yopildi: erta to'xtatildi
---
Kassa ochildi
Kassa yopildi: erta to'xtatildi
---
Kassa ochildi
Kassa yopildi: navbat tugadi
3

Uch holat:

  1. break — for...of siklni tark etishdan oldin return() ni chaqirdi.
  2. Destructuring — bitta qiymat oldi, iterator esa hali tugamagan. Destructuring ham return() ni chaqirdi.
  3. Spread — oxirigacha bordi. next() o'zi done: true berdi — return() chaqirilmadi, tozalashni next() qildi.

Shuning uchun tozalash ikki joyda yoziladi: next() dagi "tugadi" yo'lida va return() da. Sikl tanasida xato chiqsa ham for...of return() ni chaqiradi — xatolarni try / catch / finally darsida o'rganamiz.

Real kodda return() ichida nima bo'ladi? Masalan, iterator sahifadagi tugma bosilishlarini navbat bilan beradi deylik. Boshida u tugmaga hodisa tinglovchisini qo'shadi (addEventListener). Siklni tark etgan kod endi bosilishlarni o'qimaydi, lekin tinglovchi qolib ketsa, u xotirani band qilib, ishlashda davom etadi. return() dagi removeEventListener shu "osilib qolgan" ishni yopadi. Fayl va baza ulanishlarida ham xuddi shu g'oya: ochgan — yopishi shart.

6.2 return() obyekt qaytarishi shart

return() ichida return yozishni unutsangiz:

js
const kuzatuvchi = {
  [Symbol.iterator]() {
    return {
      next() {
        return { value: 1, done: false };
      },
      return() {
        console.log("Tozalandi");
      },
    };
  },
};

for (const x of kuzatuvchi) {
  break;
}

Konsolda:

text
Tozalandi
TypeError: Iterator result undefined is not an object

Tarjimasi: "iterator natijasi undefined — obyekt emas". Tozalash bajarildi, lekin return() undefined qaytardi, protokol esa obyekt kutadi. Tuzatish: return { value: undefined, done: true };.

Tekshirib ko'ring: kassaNavbati(["osh"]) ni for...of bilan oxirigacha aylansak, return() chaqiriladimi?

Javob

Yo'q. next() ikkinchi chaqiruvda o'zi done: true qaytaradi va "navbat tugadi" deb yozadi. Iterator tugagan — tashlab ketilgan emas. return() faqat iterator oxiriga yetmasdan to'xtatilganda kerak.

7. Ko'p uchraydigan xatolar

7.1 TypeError: undefined is not a function

js
const menyu = {
  [Symbol.iterator]() {
    return {};
  },
};

for (const taom of menyu) {
  console.log(taom);
}

Konsolda:

text
TypeError: undefined is not a function

Tarjimasi: "undefined — funksiya emas". Xabar chalkash: hech qayerda undefined() yozmadik. Ichkarida for...of iteratorning next xususiyatini o'qidi — {} da u yo'q, ya'ni undefined. Keyin uni chaqirishga urindi. Qoida: xabar ko'rsatgan qiymat qayerdan kelganini qidiring. Tuzatish: [Symbol.iterator]() qaytargan obyektda next() borligini tekshiring.

7.2 Metod nomini oddiy yozish

js
const soatlar = {
  iterator() {
    return [10, 11][Symbol.iterator]();
  },
};
console.log([...soatlar]);

Konsolda:

text
TypeError: soatlar is not iterable

iterator — oddiy satr kalit. JavaScript faqat symbol kalitni qidiradi: [Symbol.iterator]. Tuzatish: metod nomini kvadrat qavsga oling — [Symbol.iterator]() { ... }.

7.3 next() qiymatni to'g'ridan-to'g'ri qaytaradi

next() { return joriy++; } — { value, done } o'rniga son. Natija: TypeError: Iterator result 10 is not an object — "iterator natijasi 10 — obyekt emas". Xabardagi son — siz qaytargan qiymat. Tuzatish: har doim { value: ..., done: ... } qaytaring.

7.4 done: true hech qachon kelmaydi

js
next() {
  return { value: joriy++, done: false }; // shart yo'q!
}

Xato xabari chiqmaydi — [...obyekt] shunchaki abadiy ishlaydi, terminal qotadi (Ctrl+C bilan to'xtating). Yangi iterable'ni birinchi marta destructuring bilan sinang: const [a, b, c] = obyekt; — u faqat uchta next() chaqiradi va cheksiz bo'lsa ham qotmaydi.

7.5 next() ichida this

js
const soatlar = {
  boshi: 10,
  oxiri: 13,
  [Symbol.iterator]() {
    return {
      joriy: 0,
      next() {
        if (this.joriy < this.oxiri) {
          return { value: this.joriy++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

console.log([...soatlar]); // []

Bo'sh massiv, xato yo'q. next() iterator obyektining metodi, shuning uchun uning ichida this — iterator, soatlar emas. Iteratorda oxiri yo'q: 0 < undefined — false. Tuzatish: kerakli qiymatlarni [Symbol.iterator]() ichida (u yerda this — soatlar) closure o'zgaruvchilariga oling: let joriy = this.boshi; const oxiri = this.oxiri;. Arrow next: () => ... yozib, tashqi this ni olish ham ishlaydi, lekin closure aniqroq.

8. Mashqlar

1-mashq (oson): Ish kunlari

ishKunlari obyektini yozing: u iterable bo'lsin va «Bahor» ishlaydigan kunlarni bersin — "Dushanba" dan "Shanba" gacha (yakshanba — dam olish). Kunlar obyekt ichidagi kunlar massivida tursin, iteratorni esa qo'lda (indeks bilan) yozing. [...ishKunlari].length — 6 bo'lsin.

Yechim
js
const ishKunlari = {
  kunlar: [
    "Dushanba", "Seshanba", "Chorshanba",
    "Payshanba", "Juma", "Shanba",
  ],
  [Symbol.iterator]() {
    const kunlar = this.kunlar;
    let i = 0;
    return {
      next() {
        if (i < kunlar.length) {
          return { value: kunlar[i++], done: false };
        }
        return { value: undefined, done: true };
      },
      [Symbol.iterator]() {
        return this;
      },
    };
  },
};

console.log([...ishKunlari].length); // 6
const [birinchiKun] = ishKunlari;
console.log(birinchiKun); // Dushanba

kunlar — [Symbol.iterator]() ichida closure o'zgaruvchisiga olindi: next() ichidagi this ga tayanmaymiz. Massiv bo'lgani uchun «Qisqa yo'l: topshirish» ham to'g'ri yechim: return this.kunlar[Symbol.iterator]();.

2-mashq (o'rta): Teskari tartibda

teskari(massiv) fabrikasini yozing: u massivni oxiridan boshiga beradigan iterable qaytarsin. Massivning nusxasini olmang (toReversed yoki [...massiv].reverse() ishlatmang) — indeksni oxiridan sanang. Iterable qayta ishlatiladigan bo'lsin.

js
const buyurtmalar = ["osh", "manti", "choy"];
console.log([...teskari(buyurtmalar)]); // [ 'choy', 'manti', 'osh' ]
Yechim
js
function teskari(massiv) {
  return {
    [Symbol.iterator]() {
      let i = massiv.length - 1;
      return {
        next() {
          if (i >= 0) {
            return { value: massiv[i--], done: false };
          }
          return { value: undefined, done: true };
        },
        [Symbol.iterator]() {
          return this;
        },
      };
    },
  };
}

const buyurtmalar = ["osh", "manti", "choy"];
const oxiridan = teskari(buyurtmalar);
console.log([...oxiridan]); // [ 'choy', 'manti', 'osh' ]
console.log([...oxiridan]); // [ 'choy', 'manti', 'osh' ]
console.log(buyurtmalar); // [ 'osh', 'manti', 'choy' ]

i har aylanishda [Symbol.iterator]() ichida qaytadan length - 1 bo'ladi — shuning uchun ikkinchi spread ham to'liq. Asl massiv o'zgarmadi va nusxalanmadi ham: million elementli massivda ham qo'shimcha xotira kerak emas.

3-mashq (qiyin): Telefon uchun sahifalar

Telefonda menyu ikki taomdan ko'rsatiladi. sahifalar(massiv, hajm) fabrikasini yozing: u massivni hajm talik bo'laklarga bo'lib beradigan iterable qaytarsin. Oxirgi bo'lak qisqaroq bo'lishi mumkin. Bo'lakni olish uchun slice (Kesish va birlashtirish).

js
const taomlar = ["Osh", "Lag'mon", "Manti", "Somsa", "Ko'k choy"];
for (const sahifa of sahifalar(taomlar, 2)) {
  console.log(sahifa);
}
Yechim
js
function sahifalar(massiv, hajm) {
  return {
    [Symbol.iterator]() {
      let boshi = 0;
      return {
        next() {
          if (hajm > 0 && boshi < massiv.length) {
            const bolak = massiv.slice(boshi, boshi + hajm);
            boshi += hajm;
            return { value: bolak, done: false };
          }
          return { value: undefined, done: true };
        },
        [Symbol.iterator]() {
          return this;
        },
      };
    },
  };
}

const taomlar = ["Osh", "Lag'mon", "Manti", "Somsa", "Ko'k choy"];
for (const sahifa of sahifalar(taomlar, 2)) {
  console.log(sahifa);
}

Konsolda:

text
[ 'Osh', "Lag'mon" ]
[ 'Manti', 'Somsa' ]
[ "Ko'k choy" ]

slice massiv chegarasidan chiqsa, borini beradi — oxirgi sahifa bitta taomli. hajm > 0 sharti — oraliq dagi qadam > 0 kabi himoya: hajm 0 bo'lsa, boshi hech qachon o'smasdi va sikl cheksiz bo'lardi.

4-mashq: Vazifalar qadami — VazifalarRoyxati ni iterable qiling

O'tgan darsda mashqlar da tekshirdingiz: for (const v of royxat) — TypeError: royxat is not iterable. Endi vazifalar ilovasining o'zida tuzatamiz.

  1. Yangi branch: git switch -c refactor/iteratsiya.
  2. VazifalarRoyxati ga [Symbol.iterator]() metodini qo'shing. Iteratorni qo'lda yozing (indeks bilan, closure'da), o'zini qaytaradigan [Symbol.iterator]() bilan.
  3. Ilova xulqi o'zgarmasin.
Yechim

VazifalarRoyxati ichiga, toJSON() dan oldin:

js
[Symbol.iterator]() {
  const vazifalar = this.#vazifalar;
  let i = 0;
  return {
    next() {
      if (i < vazifalar.length) {
        return { value: vazifalar[i++], done: false };
      }
      return { value: undefined, done: true };
    },
    [Symbol.iterator]() {
      return this;
    },
  };
}

Iterator #vazifalar ning o'zini emas, faqat "keyingisini ber" eshigini beradi. Royxatning qolgan qoidalari (qosh id beradi, ochir kuzatuvchilarga xabar beradi) chetlab o'tilmaydi.

Node'da tekshiring — blokda royxatning faqat kerakli qismi:

js
class Vazifa {
  constructor(id, matn, bajarildi = false) {
    this.id = id;
    this.matn = matn;
    this.bajarildi = bajarildi;
  }
}

class VazifalarRoyxati {
  #vazifalar = [];

  static dan(malumotlar) {
    const yangi = new VazifalarRoyxati();
    yangi.#vazifalar = malumotlar.map(
      (m) => new Vazifa(m.id, m.matn, m.bajarildi),
    );
    return yangi;
  }

  [Symbol.iterator]() {
    const vazifalar = this.#vazifalar;
    let i = 0;
    return {
      next() {
        if (i < vazifalar.length) {
          return { value: vazifalar[i++], done: false };
        }
        return { value: undefined, done: true };
      },
      [Symbol.iterator]() {
        return this;
      },
    };
  }
}

const royxat = VazifalarRoyxati.dan([
  { id: 1, matn: "Non olish", bajarildi: false },
  { id: 2, matn: "DOM darsini takrorlash", bajarildi: true },
  { id: 3, matn: "Onamga qo'ng'iroq qilish", bajarildi: false },
]);

for (const v of royxat) {
  console.log(v.id, v.matn);
}
const [birinchi] = royxat;
console.log(birinchi.matn); // Non olish
const nusxa = [...royxat];
nusxa.length = 0;
console.log([...royxat].length); // 3

Konsolda:

text
1 Non olish
2 DOM darsini takrorlash
3 Onamga qo'ng'iroq qilish
Non olish
3

Oxirgi ikki qator — asosiy kafolat: spread nusxa berdi, nusxani bo'shatish royxatga ta'sir qilmadi. Meros darsidagi tajribada length = 0 hamma vazifani o'chirib yuborgan edi.

Brauzerda:

  • Ilova avvalgidek ishlashini tekshiring: uch vazifa, "2 ta vazifa qoldi, 1 ta bajarildi", qo'shish, belgilash, tahrirlash (Enter, Esc), o'chirish, uch filtr, takror matn xatosi, fokus.
  • DevTools Console'da: [...holat.vazifalar].map((v) => v.matn) — vazifa matnlari massivi. for (const v of holat.vazifalar) console.log(v.id) — id'lar.
  • holat.vazifalar.length — undefined: royxat hali ham massiv emas, faqat "aylansa bo'ladigan" to'plam.

Hozir ilova kodining o'zida royxatni tashqaridan aylanadigan joy yo'q: render korinadiganlar() dan foydalanadi. Lekin royxat endi tilning umumiy "aylanish tili"da gapiradi. Ilova fayllarga bo'linganda (modullar darslari) boshqa fayllardagi kod uni ichini ochmasdan aylana oladi.

Commit: Ro'yxat: VazifalarRoyxati ni iterable qil. Branch'ni hali main ga qo'shmang — keyingi ikki darsda bu iteratorni ancha qisqa yozamiz.

9. Real ishda

  • Kutubxonalardagi to'plamlar. Kutubxona — boshqalar yozgan, loyihaga qo'shib ishlatiladigan tayyor kod (npm orqali o'rnatiladi, kursda alohida o'rganamiz). Masalan, Immutable.js'ning List va Map lari, ko'plab daraxt va graf kutubxonalari [Symbol.iterator]() ni amalga oshiradi — ular massiv bo'lmasa ham for...of va spread bilan ishlaydi. Algoritmlar qismida (14-qism) o'z bog'langan ro'yxatingizni yozganda ham shunday qilasiz.
  • range kutubxonalarda. Lodash (mashhur yordamchi funksiyalar kutubxonasi) ning _.range(10, 22) funksiyasi tayyor massiv qaytaradi — hamma son darhol xotirada. Bugungi oraliq esa sonlarni so'ralganda beradi. Kichik oraliqda farq yo'q, lekin "1 dan milliongacha" yoki "cheksiz" kerak bo'lsa, faqat iterable yo'li ishlaydi. Bu farqni intervyuda aytib bera olish — yaxshi belgi.
  • DOM. NodeList, HTMLCollection, FormData — massiv emas, lekin [Symbol.iterator]() tufayli for...of bilan aylanadi. Bugun xuddi shu mexanizmni o'zingiz yozdingiz.
  • Resurslarni yopish. return() — fayl, baza ulanishi, tarmoq oqimi iteratorlarida "yarim yo'lda to'xtasa ham yop" kafolati. Bu g'oyani using darsida yangi sintaksis bilan ko'rasiz.
  • Intervyu. "Obyektni for...of bilan aylanadigan qiling", "range funksiyasini yozing" — o'rta darajadagi intervyularda ko'p beriladi. Keyingi so'raladigan savol: "Generator bilan qisqaroq yoza olasizmi?" — keyingi darsning mavzusi.

Xulosa

  • Iterable yasash: [Symbol.iterator]() metodi yangi iterator qaytaradi, iteratorda next() → { value, done }.
  • Holatni (joriy) [Symbol.iterator]() ichida, closure'da saqlang — iterable qayta ishlatiladigan bo'ladi. Holat obyektning o'zida bo'lsa — bir martalik.
  • Iteratorga o'zini qaytaradigan [Symbol.iterator]() qo'shing — u ham for...of bilan ishlasin.
  • Klassda: [Symbol.iterator]() { ... } metodi; ichki massiv bo'lsa — uning iteratoriga topshiring. Iterator ichki ma'lumotni yashirgan holda aylanishga ruxsat beradi.
  • return() — break yoki to'liq bo'lmagan destructuring'da chaqiriladi; tozalash uchun; obyekt qaytarishi shart.

Keyingi dars: Generator funksiyalar — function* va yield bilan bugungi o'n besh qatorli iteratorni uch qatorda yozamiz.

Manbalar

  • MDN: "Iteration protocols" — "The iterator protocol", "Making iterables" — developer.mozilla.org
  • MDN: "Iterator.prototype.return()" (protokol tavsifi) — developer.mozilla.org
  • ECMAScript spetsifikatsiyasi: "IteratorClose" — tc39.es/ecma262
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript'da o'z iterable obyektingizni yasash: Symbol.iterator qo'lda — IlmHamroh