IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur50/65-dars21 daqiqa
Mundarija (39)

JavaScript iteratsiya protokoli: for...of ichida nima bo'ladi

Qisqacha: for...of, spread ([...x]) va massiv destructuring bitta kelishuvga tayanadi — iteratsiya protokoliga. Qiymatlar to'plami (iterable) [Symbol.iterator]() metodi bilan iterator beradi. Iterator esa har next() chaqiruvida { value, done } obyektini qaytaradi. for...of shu next() ni done: true kelguncha qayta-qayta chaqiradi.

Bu darsda

  • Iterable va iterator farqini tushuntira olasiz.
  • Iteratorni qo'lda next() bilan aylanib, { value, done } natijasini o'qiy olasiz.
  • for...of, spread va destructuring ichida nechta next() chaqirilishini ko'rasiz.
  • Qaysi o'rnatilgan turlar iterable ekanini bilasiz va buni kod bilan tekshira olasiz.
  • x is not iterable xatosini o'qib, tuzata olasiz.

Oldin bilishingiz kerak: Massiv bo'ylab yurish, Massiv destructuring, Spread va rest, Map, Well-known symbols.

1. Nega bu kerak?

for...of ni 08-qismdan beri ishlatib kelyapsiz. U massivda ishlaydi, satrda ishlaydi, Map da ham ishlaydi. 09-qismda u querySelectorAll qaytargan NodeList ni ham aylangan edi. Lekin «Bahor» menyusini oddiy obyekt qilib yozsangiz:

js
const menyu = { osh: 35000, manti: 30000 };

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

Konsolda:

text
TypeError: menyu is not iterable

Nega massiv "iterable", obyekt esa yo'q? Bu savol kursda bir necha marta chiqdi va har safar "keyinroq ochamiz" degan edik:

  • Massiv bo'ylab yurish darsida entries() natijasi konsolda Object [Array Iterator] {} bo'lib chiqqan edi.
  • Massivga o'xshash obyektlar darsida "birma-bir berish qobiliyati" degan ibora bor edi.
  • Map darsida: "for...of nega massivda ham, Map da ham ishlaydi?"

Uchala savolning javobi bitta. Bugun shu javobni ochamiz.

Hayotdan o'xshatish: kitob va xatcho'p. Kitob — o'qisa bo'ladigan narsa, ichida betlar bor. Xatcho'p — qayerda to'xtaganingizni eslaydigan qog'oz tasma. Siz xatcho'pdan "keyingi bet"ni so'raysiz, u beradi va bir bet oldinga suriladi. Kitob tugasa, xatcho'p "tugadi" deydi. Bitta kitobga ikki kishi ikkita xatcho'p qo'yib, bir-biriga xalaqit bermay o'qiy oladi.

JavaScript'da kitob — iterable, xatcho'p — iterator. for...of esa o'quvchi: xatcho'p oladi va "keyingisi" deb so'rayveradi.

2. Iteratorni qo'lda aylantiramiz

2.1 Symbol.iterator — xatcho'p beradigan metod

Well-known symbols darsida Symbol.iterator bilan tanishgansiz: bu JavaScript'ning o'zi oldindan e'lon qilgan maxsus symbol. Massivda shu symbol kalitida metod turadi. Uni chaqirib ko'ramiz:

js
const narxlar = [35000, 28000, 30000];
const iterator = narxlar[Symbol.iterator]();

console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Konsolda:

text
Object [Array Iterator] {}
{ value: 35000, done: false }
{ value: 28000, done: false }
{ value: 30000, done: false }
{ value: undefined, done: true }

Qatorma-qator:

  1. narxlar[Symbol.iterator] — kaliti symbol bo'lgan xususiyat. Uni nuqta bilan yozib bo'lmaydi, faqat kvadrat qavs bilan (Computed kalitlar). Ichida — funksiya, oxiridagi () uni chaqiradi.
  2. Natija — Object [Array Iterator] {}. Bu iterator: massivning o'zi emas, uning ustidagi "xatcho'p".
  3. Har next() bitta qiymat beradi va xatcho'pni bir qadam suradi.
  4. Qiymatlar tugagach — { value: undefined, done: true }.

Esingizdami, Massiv bo'ylab yurish darsida values() ham xuddi shunday Object [Array Iterator] {} qaytargan edi. Bu tasodif emas — ular bitta metod:

js
const narxlar = [35000, 28000];
console.log(narxlar[Symbol.iterator] === narxlar.values); // true

2.2 { value, done } — iterator natijasi

next() har safar ikki xususiyatli obyekt qaytaradi. Uni iterator natijasi (iterator result) deyishadi:

Xususiyat Ma'nosi
value navbatdagi qiymat
done false — hali bor; true — tugadi

Nega faqat value emas? Chunki undefined ham oddiy qiymat bo'lishi mumkin. [undefined, 5] massivida birinchi next() { value: undefined, done: false } beradi. Agar faqat value qaytsa, "massiv tugadi" va "navbatdagi qiymat undefined" ni farqlab bo'lmasdi. done bayrog'i shu ikkalasini ajratadi.

Maslahat: done: true kelgandan keyin ham next() ni chaqirsa bo'ladi — xato chiqmaydi, har safar yana { value: undefined, done: true } keladi. Iterator boshiga qaytmaydi: o'qilgan kitobga qo'yilgan xatcho'p oxirgi betda qoladi.

Tekshirib ko'ring: const it = "osh"[Symbol.iterator](); dan keyin it.next() ni to'rt marta chaqirsak, to'rtinchisi nima qaytaradi?

Javob

{ value: undefined, done: true }. Satr ham iterable: birinchi uch chaqiruv "o", "s", "h" harflarini beradi, to'rtinchisi "tugadi" deydi. Satr iteratori konsolda Object [String Iterator] {} bo'lib ko'rinadi.

2.3 Iterable va iterator — ikki xil rol

Endi ikki atamani aniq ajratamiz.

Iterable (inglizcha "aylanib chiqsa bo'ladigan") — [Symbol.iterator]() metodi bor obyekt. Bu metod har chaqirilganda iterator qaytaradi. Massiv, satr, Map — iterable. Bu kitob.

Iterator — next() metodi bor obyekt. next() har chaqirilganda { value, done } qaytaradi va o'zi qayerda turganini eslab qoladi. Bu xatcho'p.

Iteratsiya protokoli (iteration protocol) — shu ikki rol haqidagi kelishuv. "Protokol" — ikki tomon oldindan kelishib olgan qoida: kim qaysi metodni beradi, u nima qaytaradi. Protokol klass ham, funksiya ham emas. Qoidaga amal qilgan har qanday obyekt for...of bilan ishlaydi.

Diagrammada for...of qanday ishlashini kuzating:

flowchart TD
  A["for (const x of taomlar)"] --> B["taomlar[Symbol.iterator]()"]
  B --> C["iterator"]
  C --> D["iterator.next()"]
  D --> E{"done?"}
  E -- "false" --> F["x = value<br/>sikl tanasi"]
  F --> D
  E -- "true" --> G["sikl tugadi"]

[Symbol.iterator]() faqat bir marta, boshida chaqiriladi. Keyin faqat next() aylanadi.

Yana bir nozik joy: massiv iteratorining o'zi ham iterable. Uning [Symbol.iterator]() metodi o'zini qaytaradi:

js
const taomlar = ["osh", "manti"];
const iterator = taomlar[Symbol.iterator]();

console.log(iterator[Symbol.iterator]() === iterator); // true

Shuning uchun for (const x of taomlar.values()) ham ishlaydi: for...of iteratordan "xatcho'p" so'raydi, u esa o'zini beradi.

Tekshirib ko'ring: Massiv iterable mi, iterator mi? narxlar.values() chi?

Javob

Massiv — iterable: unda [Symbol.iterator]() bor, lekin next() yo'q (narxlar.next — undefined). narxlar.values() — iterator: unda next() bor. U o'zini qaytaradigan [Symbol.iterator]() ga ham ega, shuning uchun bir vaqtda iterable ham.

3. for...of ichida nima bo'ladi

3.1 for...of ni qo'lda yozamiz

Diagrammadagi qadamlarni oddiy while sikli bilan yozib ko'ramiz:

js
const taomlar = ["osh", "lag'mon", "manti"];
const iterator = taomlar[Symbol.iterator]();

let natija = iterator.next();
while (!natija.done) {
  const taom = natija.value;
  console.log(taom);
  natija = iterator.next();
}

Konsolda:

text
osh
lag'mon
manti

Bu kod for (const taom of taomlar) { console.log(taom); } bilan bir xil ishni qiladi. for...of — shu sakkiz qatorning qisqa yozuvi, xolos. JavaScript dvigateli next() chaqiruvlarini va done tekshiruvini siz uchun yashirib bajaradi.

3.2 Qadamma-qadam

Keyingi ni bosib, ikki taomli massivda har next() nima qaytarishini va sikl qachon to'xtashini kuzating:

Asosiy kuzatuv: n ta element uchun next() n + 1 marta chaqiriladi. Oxirgi chaqiruv qiymat bermaydi — faqat "tugadi" deydi.

3.3 break — erta to'xtash

for...of ichida break bo'lsa, qolgan next() lar chaqirilmaydi:

js
const narxlar = [35000, 28000, 30000, 5000];

for (const narx of narxlar) {
  if (narx < 30000) {
    console.log(`Birinchi arzoni: ${narx}`);
    break;
  }
}

Konsolda:

text
Birinchi arzoni: 28000

Iterator ikki marta so'raldi — 35000 va 28000. Qolgan ikki element umuman o'qilmadi. Bu oddiy massivda kichik tejash. Lekin keyingi darslarda qiymatlarni so'ralgandagina hisoblaydigan iteratorlarni ko'ramiz — u yerda bu farq juda katta bo'ladi.

Tekshirib ko'ring: Uch elementli massivni for...of bilan oxirigacha aylansak, next() necha marta chaqiriladi? break bilan birinchi elementda chiqsak-chi?

Javob

Oxirigacha — 4 marta: uchta qiymat uchun va bitta "tugadi" (done: true) uchun. Birinchi elementda break — 1 marta: birinchi qiymat olindi, keyin sikl to'xtadi va boshqa so'ralmadi.

4. Spread, destructuring va boshqalar ham shu protokolda

4.1 Kuzatuvchi yordamchi

Ichkarida nechta next() chaqirilishini ko'rish uchun kichik yordamchi yozamiz. U istalgan iterable'ni oladi va har next() ni konsolga yozib turadi. Keyin uni destructuring va spread bilan sinaymiz:

js
function kuzat(iterable) {
  const ichki = iterable[Symbol.iterator]();
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      const natija = ichki.next();
      console.log("next() →", natija);
      return natija;
    },
  };
}

const taomlar = ["osh", "lag'mon", "manti"];

const [birinchi, ikkinchi] = kuzat(taomlar);
console.log(birinchi, ikkinchi);
console.log("---");
const nusxa = [...kuzat(taomlar)];
console.log(nusxa.length);

Konsolda:

text
next() → { value: 'osh', done: false }
next() → { value: "lag'mon", done: false }
osh lag'mon
---
next() → { value: 'osh', done: false }
next() → { value: "lag'mon", done: false }
next() → { value: 'manti', done: false }
next() → { value: undefined, done: true }
3

kuzat — o'zimiz yasagan iterator: unda next() ham, o'zini qaytaradigan [Symbol.iterator]() ham bor. Ishni esa haqiqiy iteratorga (ichki) topshiradi, faqat o'rtada "josuslik" qiladi. Bunday obyektlarni qanday yasashni keyingi darsda batafsil o'rganamiz. Hozir uni tayyor asbob sifatida ishlating va chiqishning ikki yarmiga qarang.

4.2 Destructuring — faqat kerakligicha

"---" dan yuqorisi: ikki o'zgaruvchi — ikki next(). Uchinchi taom so'ralmadi ham. Massiv destructuring aslida "massivni" emas, istalgan iterable'ni ochadi: u indeks bo'yicha emas, next() bilan birma-bir oladi. Shuning uchun satrni ham ochsa bo'ladi: const [harf] = "Bahor"; — harf ga "B" tushadi.

4.3 Spread va rest — oxirigacha

"---" dan pastki qism: spread done: true kelguncha so'radi — uch qiymat va bitta "tugadi". Destructuring'dagi rest (const [bosh, ...qolgan] = ...) ham xuddi shunday: birinchisini oladi, qolganini oxirigacha yig'adi.

4.4 Iterable qabul qiladigan joylar

Bitta protokol — juda ko'p joyda ishlaydi:

Yozuv Nima qiladi
for (const x of it) har qiymat uchun sikl
[...it], f(...it) hammasini massivga yoki argumentlarga yoyadi
const [a, b] = it kerakligicha next()
Array.from(it) massiv yasaydi
new Map(it), new Set(it) juftliklardan / qiymatlardan to'plam
Object.fromEntries(it) [kalit, qiymat] juftliklaridan obyekt

Masalan, Math.max(...new Set([3, 9, 2])) — 9: Set iterable, spread uni argumentlarga yoydi. Object.fromEntries(menyu) esa Map ni oddiy obyektga aylantiradi. Ularning hech biri "bu massivmi?" deb so'ramaydi — faqat "Symbol.iterator bormi?" deb so'raydi.

Diqqat: Obyekt ichidagi spread { ...x } boshqa mexanizm. U iteratsiya protokolini ishlatmaydi — obyektning o'z xususiyatlarini ko'chiradi (Spread va rest). Shuning uchun { ...menyu } oddiy obyektda ishlaydi, [...menyu] esa TypeError beradi. Kvadrat qavs — protokol, jingalak qavs — xususiyatlar.

Tekshirib ko'ring: const [a, b, c, d] = ["osh", "manti"]; qatorida next() necha marta chaqiriladi va d nima bo'ladi?

Javob

Uch marta. Birinchi ikkisi "osh" va "manti" ni berdi, uchinchisi done: true qaytardi. Iterator tugaganini bilgach, destructuring boshqa so'ramaydi: c va d — ikkalasi undefined. "Tugadi" degan iteratordan qayta so'rash ma'nosiz.

5. Qaysi qiymatlar iterable?

5.1 Ro'yxat

Kursda uchragan turlarni ikkiga ajratamiz:

Iterable Iterable emas
massiv, satr oddiy obyekt {}
Map, Set son, mantiqiy qiymat
arguments, NodeList null, undefined
typed array'lar WeakMap, WeakSet
keys(), values(), entries() natijalari

Map aylanganda [kalit, qiymat] juftliklarini beradi. Shuning uchun for...of ichida destructuring qulay:

js
const menyu = new Map([
  ["osh", 35000],
  ["manti", 30000],
]);

for (const [taom, narx] of menyu) {
  console.log(`${taom}: ${narx} so'm`);
}

Konsolda:

text
osh: 35000 so'm
manti: 30000 so'm

Har aylanishda iterator bitta massiv (["osh", 35000]) beradi, destructuring esa uni ikki o'zgaruvchiga ochadi.

5.2 Satr — harflar bo'yicha, kod birliklari bo'yicha emas

Satr iteratori bitta nozik ishni to'g'ri qiladi:

js
const buyurtma = "choy🍵";

console.log(buyurtma.length); // 6
console.log([...buyurtma]); // [ 'c', 'h', 'o', 'y', '🍵' ]

length — 6, lekin spread beshta belgi berdi. Emoji xotirada ikki "bo'lak"dan (kod birligidan) iborat, length esa bo'laklarni sanaydi. Satr iteratori esa bo'laklarni juftlab, butun belgi beradi. Shuning uchun emoji yoki kamyob harfli matnda belgilarni sanash uchun [...matn].length ishonchliroq.

5.3 Tekshiruvchi funksiya

"Bu qiymat iterable mi?" — degan savolga javob: Symbol.iterator kalitida funksiya bormi?

js
function iterablemi(qiymat) {
  return (
    qiymat != null &&
    typeof qiymat[Symbol.iterator] === "function"
  );
}

console.log(iterablemi(["osh"])); // true
console.log(iterablemi("Bahor")); // true
console.log(iterablemi(new Map())); // true
console.log(iterablemi({ osh: 35000 })); // false
console.log(iterablemi(42)); // false
console.log(iterablemi(null)); // false

qiymat != null — avval null va undefined ni chetlatadi: ularning xususiyatini o'qishning o'zi TypeError beradi. != bu yerda ataylab ikki belgili: u null ni ham, undefined ni ham ushlaydi. 42 uchun esa 42[Symbol.iterator] — undefined, xato chiqmaydi.

5.4 Oddiy obyektni qanday aylanamiz?

Obyekt iterable emas, chunki "nimani aylanish kerak?" degan savolga bitta javob yo'q: kalitlarnimi, qiymatlarnimi, juftliklarnimi? JavaScript taxmin qilmaydi — siz tanlaysiz. Obyekt bo'ylab yurish darsidagi uch metod massiv qaytaradi, massiv esa iterable:

js
const menyu = { osh: 35000, manti: 30000 };

for (const [taom, narx] of Object.entries(menyu)) {
  console.log(taom, narx);
}

Konsolda:

text
osh 35000
manti 30000

Obyektni o'z iterable'ingizga aylantirish ham mumkin — [Symbol.iterator] metodini o'zingiz yozib. Buni keyingi darsda qilamiz.

Tekshirib ko'ring: iterablemi(new Set([1, 2]).values()) nima qaytaradi? Nega?

Javob

true. values() — iterator, va o'rnatilgan iteratorlarning hammasi o'zini qaytaradigan [Symbol.iterator]() metodiga ega. Demak ular bir vaqtda iterable ham. Shuning uchun for (const x of to'plam.values()) ishlaydi.

6. Iterator — bir martalik va "jonli"

6.1 Iterator bir marta o'qiladi

Massivni xohlagancha spread qilish mumkin: har safar yangi iterator yaratiladi. Iteratorni esa — faqat bir marta:

js
const taomlar = ["osh", "lag'mon", "manti"];
const iterator = taomlar.values();

console.log([...iterator]); // [ 'osh', "lag'mon", 'manti' ]
console.log([...iterator]); // []

Ikkinchi spread bo'sh massiv berdi, xato esa chiqmadi. Xatcho'p allaqachon oxirgi betda — undan "keyingi bet" so'rasangiz, "tugadi" deydi. Qoida: iterable — qayta o'qiladi, iterator — bir martalik. Iteratorni o'zgaruvchiga saqlab, ikki joyda ishlatmang.

6.2 Iterator nusxa olmaydi

Massiv iteratori massivning nusxasini saqlamaydi. U faqat "qaysi o'rindaman" degan raqamni eslaydi va har next() da massivga qaytadan qaraydi:

js
const buyurtma = ["osh"];
const iterator = buyurtma.values();

buyurtma.push("somsa");
console.log([...iterator]); // [ 'osh', 'somsa' ]

Iterator push dan oldin yaratilgan, lekin "somsa" ni ham berdi. Bu "jonli" bog'lanish kitob-xatcho'p o'xshatishiga ham mos: kimdir kitob oxiriga yangi bet tiksa, xatcho'p bilan o'qiyotgan odam unga ham yetib boradi.

Shu sabab for...of ichida aylanayotgan massivga element qo'shish xavfli. Har aylanishda bitta qo'shilsa, iterator hech qachon oxiriga yetmaydi — sikl cheksiz aylanadi. Callback imzosi darsidagi qoida bu yerda ham amal qiladi: aylanayotgan massivni o'zgartirmang.

6.3 Iterator oldindan hisoblamaydi

Yuqoridagi ikki misoldan muhim xulosa chiqadi: iterator qiymatni so'ralgan paytda oladi. U boshida "hamma qiymatlar ro'yxati"ni tayyorlab qo'ymaydi. Hozircha bu massiv ichidan olishdek oddiy ish. Lekin protokol qiymat qayerdan kelishini umuman so'ramaydi — next() uni massivdan ham olishi, hisoblab chiqarishi ham mumkin.

Tasavvur qiling: «Bahor»dagi navbat chiptalari apparati. Unda oldindan bosilgan million chipta yo'q — tugmani bosganingizda keyingi raqamni bosib beradi. Bu ham iterator: har "keyingisi" so'rovida yangi qiymat. U hech qachon "tugadi" demasligi ham mumkin. Bunday iteratordan [...x] bilan hammasini olishga urinsangiz, dastur abadiy kutib qoladi. for...of dagi break yoki ikki o'zgaruvchili destructuring esa bemalol ishlaydi — ular kerakligicha so'raydi.

Bunday "talab bo'yicha" iteratorlarni Generator funksiyalar darsida bir necha qatorda yozishni o'rganamiz. Bugungi darsning asosiy foydasi shu yerda: protokolni bilsangiz, generator ham, Map ham, NodeList ham bir xil tushuniladi.

Tekshirib ko'ring: Navbat chiptalari apparati kabi cheksiz iterator bor deylik. const [a, b] = apparat; ishlaydimi? console.log([...apparat]) chi?

Javob

Destructuring ishlaydi: ikki o'zgaruvchi — ikki next(), keyin to'xtaydi. Spread esa done: true ni kutadi, u esa hech qachon kelmaydi — dastur qotib qoladi. Spread, Array.from va oxirigacha boradigan for...of faqat tugaydigan iteratorlar uchun.

7. Ko'p uchraydigan xatolar

7.1 TypeError: menyu is not iterable

«Nega bu kerak?» bo'limidagi xato. Tarjimasi: "menyu — iterable emas". Xabarda sizning o'zgaruvchingiz nomi turadi. for...of, [...menyu] yoki const [a] = menyu — uchalasi ham shu xabarni beradi, chunki uchalasi ham menyu[Symbol.iterator] ni qidiradi va topmaydi.

Tuzatish: oddiy obyekt bo'lsa — Object.entries(menyu), Object.keys(menyu) yoki Object.values(menyu). Qiymat null yoki undefined bo'lib qolgan bo'lsa (masalan, funksiya hech narsa qaytarmagan) — xato boshqa joyda: o'zgaruvchiga qiymat qayerdan kelganini tekshiring.

7.2 new Map(obyekt) — boshqa so'zlar, bir xil sabab

js
const menyu = { osh: 35000 };
const xarita = new Map(menyu);

Konsolda:

text
TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

Tarjimasi: "obyekt iterable emas (Symbol(Symbol.iterator) xususiyatini o'qib bo'lmadi)". Bu safar xabarda o'zgaruvchi nomi yo'q: Map konstruktori qiymatning turini ("object") aytadi, xolos. Qavs ichidagi qism aynan nimani qidirganini ochiq aytyapti — Symbol.iterator ni.

Tuzatish: new Map(Object.entries(menyu)) — entries juftliklar massivini beradi, Map aynan shuni kutadi.

7.3 Kvadrat va jingalak qavsni chalkashtirish

{ ...menyu } ishlagani uchun [...menyu] ham ishlaydi deb o'ylash. Birinchisi xususiyatlarni ko'chiradi, ikkinchisi iteratsiya protokolini ishlatadi. Teskarisi ham kutilmagan natija beradi: { ..."ab" } — { '0': 'a', '1': 'b' }, harflar indeks kalitli xususiyatlarga aylanadi.

7.4 Iteratorni ikki marta ishlatish

«Iterator bir marta o'qiladi» bo'limidagi holat. Eng yomoni — xato chiqmaydi, ikkinchi aylanish shunchaki bo'sh bo'ladi. Ma'lumot "yo'qolib qoldi" deb uzoq qidirasiz. Tuzatish: iterator emas, iterable'ni (massivni, Map ni) saqlang va kerak bo'lganda undan yangi iterator oling.

7.5 Array.from jim qoladi

js
const menyu = { osh: 35000 };
console.log(Array.from(menyu)); // []

Array.from avval iterable'mi deb tekshiradi, bo'lmasa massivga o'xshashmi deb length ga qaraydi (Massivga o'xshash obyektlar). menyu da length yo'q — natija bo'sh massiv, xato yo'q. for...of darrov xato bergan joyda Array.from jim qoladi. Tuzatish: obyektni Object.entries bilan oching.

7.6 for...in ni for...of o'rniga yozish

js
const taomlar = ["osh", "manti"];

for (const t in taomlar) {
  console.log(t, typeof t);
}

Konsolda:

text
0 string
1 string

Taom nomlari o'rniga satr ko'rinishidagi indekslar chiqdi. for...in iteratsiya protokolini ishlatmaydi — u obyekt kalitlarini sanaydi (Obyekt bo'ylab yurish). Massivda kalitlar — "0", "1". Tuzatish: qiymatlar kerak bo'lsa — for...of. Eslab qolish uchun: in — indeks va kalitlar, of — qiymatlar.

8. Mashqlar

1-mashq (oson): Xatcho'pni qo'lda suring

Quyidagi kod nima chiqarishini avval qog'ozga yozing, keyin ishga tushirib tekshiring. Eslatma: Set har qiymatni bir marta saqlaydi — ikkinchi "osh" qo'shilmaydi.

js
const buyurtmalar = new Set(["osh", "manti", "osh"]);
const it = buyurtmalar.values();
console.log(it.next());
console.log(it.next());
console.log(it.next());
Yechim
js
const buyurtmalar = new Set(["osh", "manti", "osh"]);
const it = buyurtmalar.values();
console.log(it.next());
console.log(it.next());
console.log(it.next());

Konsolda:

text
{ value: 'osh', done: false }
{ value: 'manti', done: false }
{ value: undefined, done: true }

Set da faqat ikki qiymat qoldi — takror "osh" tushib qoldi. Uchinchi next() "tugadi" dedi.

2-mashq (o'rta): Birinchi N tasi

birinchiN(iterable, n) funksiyasini yozing: u iterable'dan ko'pi bilan n ta qiymatni massivga olib qaytarsin. for...of ishlatmang — iteratorni [Symbol.iterator]() bilan oling va next() ni o'zingiz chaqiring (while sikli, «for...of ni qo'lda yozamiz» bo'limidagi kabi). Iterable n dan qisqa bo'lsa, borini qaytarsin.

js
console.log(birinchiN("Bahor", 3)); // [ 'B', 'a', 'h' ]
Yechim
js
function birinchiN(iterable, n) {
  const iterator = iterable[Symbol.iterator]();
  const natija = [];
  while (natija.length < n) {
    const qadam = iterator.next();
    if (qadam.done) {
      break;
    }
    natija.push(qadam.value);
  }
  return natija;
}

console.log(birinchiN("Bahor", 3)); // [ 'B', 'a', 'h' ]
console.log(birinchiN(["osh"], 3)); // [ 'osh' ]
const menyu = new Map([["osh", 35000], ["manti", 30000]]);
console.log(birinchiN(menyu, 1)); // [ [ 'osh', 35000 ] ]

Ikki shart siklni to'xtatadi: yetarli qiymat yig'ildi (natija.length < n yolg'on) yoki iterator tugadi (done). Funksiya n tadan ortiq next() chaqirmaydi. Shu xususiyat keyinroq juda muhim bo'ladi: Generator funksiyalar darsida cheksiz iteratorlarni ko'ramiz — ulardan [...x] bilan hammasini olib bo'lmaydi, birinchiN esa bemalol oladi.

3-mashq (qiyin): O'z for...of ingiz

harBiriUchun(iterable, callback) funksiyasini yozing. U for...of ning qo'lda yozilgan nusxasi bo'lsin: iterable'dan iterator olsin va har qiymat uchun callback(qiymat, tartibRaqami) ni chaqirsin (tartibRaqami 0 dan). for...of ishlatmang. So'ng uni massiv, satr va Map da sinang.

Yechim
js
function harBiriUchun(iterable, callback) {
  const iterator = iterable[Symbol.iterator]();
  let tartib = 0;
  let qadam = iterator.next();
  while (!qadam.done) {
    callback(qadam.value, tartib);
    tartib++;
    qadam = iterator.next();
  }
}

harBiriUchun(["osh", "manti"], (taom, i) => {
  console.log(i, taom);
});
harBiriUchun("ok", (harf) => console.log(harf));
const menyu = new Map([["choy", 5000]]);
harBiriUchun(menyu, ([taom, narx]) => {
  console.log(`${taom} — ${narx}`);
});

Konsolda:

text
0 osh
1 manti
o
k
choy — 5000

Map uchun callback parametrida destructuring: har qiymat [kalit, qiymat] massivi. Nima uchun "indeks" emas, "tartib raqami"? Chunki satr, Map yoki Set da indeks degan narsa yo'q — iterator faqat "navbatdagisi"ni biladi. Tartibni sanashni biz o'zimiz qildik.

4-mashq: Vazifalar qadami — royxat iterable mi? (mashqlar repo)

Bu dars vazifalar ilovasini o'zgartirmaydi — qadam mashqlar reposida va u keyingi darsga tayyorgarlik. kurs/mashqlar/10/50-iteratsiya/royxat.js faylini yarating.

OOP tamoyillari va kompozitsiya darsida VazifalarRoyxati ni yasagan edingiz: vazifalar #vazifalar massivida yashirin. Faylga klassning qisqa nusxasini ko'chiring (Vazifa — faqat id, matn, bajarildi; royxat — #vazifalar, static dan, korinadiganlar(filtr)). So'ng shu darsdagi iterablemi bilan tekshiring:

  1. royxat iterable mi?
  2. royxat.korinadiganlar("hammasi") chi?
  3. Barcha vazifa matnlarini chiqarish uchun hozir qanday yozish kerak?
Yechim
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;
  }

  korinadiganlar(filtr) {
    if (filtr === "hammasi") {
      return this.#vazifalar.filter(() => true);
    }
    const bajarildimi = filtr === "bajarilgan";
    return this.#vazifalar.filter(
      (v) => v.bajarildi === bajarildimi,
    );
  }
}

function iterablemi(qiymat) {
  return (
    qiymat != null &&
    typeof qiymat[Symbol.iterator] === "function"
  );
}

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

console.log(iterablemi(royxat)); // false
console.log(iterablemi(royxat.korinadiganlar("hammasi"))); // true

for (const v of royxat.korinadiganlar("hammasi")) {
  console.log(v.matn);
}

Konsolda:

text
false
true
Non olish
DOM darsini takrorlash

Royxatning o'zi iterable emas: for (const v of royxat) yozsangiz — TypeError: royxat is not iterable. Barcha vazifani aylanish uchun hozir korinadiganlar("hammasi") ni chaqirish kerak. Bu ikki noqulaylik beradi: birinchidan, nom ma'noga mos emas ("ko'rinadiganlar" emas, "hammasi" kerak); ikkinchidan, filter har safar butun massivning nusxasini yasaydi — faqat aylanib chiqish uchun.

Keyingi darsda VazifalarRoyxati ga [Symbol.iterator]() qo'shamiz. Shunda for (const v of holat.vazifalar) to'g'ridan-to'g'ri ishlaydi, #vazifalar esa baribir yashirin qoladi.

Commit (mashqlar repo): 10/50: VazifalarRoyxati iterable emasligini tekshir.

9. Real ishda

  • Har kuni, sezilmasdan. Har for...of, har [...massiv, yangi] va har destructuring — iteratsiya protokoli. React'da (interfeys yasash kutubxonasi, 17-qismda o'rganamiz) holatni yangilashda — [...vazifalar, yangiVazifa] — ham shu ishlaydi.
  • Brauzer API'lari. NodeList, FormData (for (const [nom, qiymat] of formData)), URLSearchParams — hammasi iterable. Ularning har biri uchun alohida "aylanish usuli"ni yodlash shart emas: protokol bitta.
  • Katta ma'lumot. Node.js'da faylni qatorma-qator o'qish, serverdan sahifalab ma'lumot olish — iteratorlar ustiga qurilgan. Faqat u yerda qiymatlar vaqt o'tib keladi; buni Asinxron iteratsiya darsida ko'rasiz.
  • Intervyu. "Iterable va iterator farqi nima?", "Nega oddiy obyekt for...of bilan ishlamaydi?", "for...in va for...of farqi?" — ko'p beriladigan savollar. Endi javobingiz "Symbol.iterator va next()" bilan boshlanadi.

Xulosa

  • Iterable — [Symbol.iterator]() metodi bor obyekt; iterator — next() metodi bor obyekt.
  • next() har safar { value, done } qaytaradi; done: true — qiymatlar tugadi.
  • for...of, spread, massiv destructuring, Array.from, new Map/Set — hammasi shu protokol. Destructuring faqat kerakligicha next() chaqiradi.
  • Massiv, satr, Map, Set, NodeList — iterable; oddiy obyekt — yo'q (Object.entries bilan oching).
  • Iterable qayta o'qiladi, iterator — bir martalik. Massiv iteratori nusxa olmaydi, massivni "jonli" ko'radi.
  • { ...x } — xususiyatlar, [...x] — protokol.

Keyingi dars: O'z iterable obyektingizni yasash — [Symbol.iterator]() va next() ni o'zingiz yozib, istalgan obyektni for...of bilan aylanadigan qilasiz.

Manbalar

  • MDN: "Iteration protocols" — developer.mozilla.org
  • MDN: "for...of", "Symbol.iterator" — developer.mozilla.org
  • ECMAScript spetsifikatsiyasi: "Iteration" bo'limi — tc39.es/ecma262
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript iteratsiya protokoli: for...of ichida nima bo'ladi — IlmHamroh