Yangi JavaScript Iterator `scan` yordamchisining kuchini kashf eting. Uning oqimlarni qayta ishlash, holatni boshqarish va ma'lumotlarni yig'ishda `reduce` dan tashqari inqilob qilishini o'rganing.
JavaScript Iterator `scan`: Akkumulyativ oqimlarni qayta ishlash uchun yetishmayotgan bo'g'in
Doimiy rivojlanib borayotgan zamonaviy veb-ishlab chiqish olamida ma'lumotlar qirol hisoblanadi. Biz doimo ma'lumotlar oqimlari bilan ishlaymiz: foydalanuvchi hodisalari, real vaqtdagi API javoblari, katta ma'lumotlar to'plamlari va boshqalar. Bu ma'lumotlarni samarali va deklarativ tarzda qayta ishlash eng asosiy vazifadir. Ko'p yillar davomida JavaScript dasturchilari massivni bitta qiymatga qisqartirish uchun kuchli Array.prototype.reduce metodiga tayanishgan. Ammo agar sizga faqat manzil emas, balki butun sayohatni ko'rish kerak bo'lsa-chi? Agar sizga to'planishning har bir oraliq qadamini kuzatish kerak bo'lsa-chi?
Aynan shu yerda sahnaga yangi, kuchli vosita kirib keladi: Iterator scan yordamchisi. Hozirda 3-bosqichda bo'lgan TC39 Iterator Helpers taklifining bir qismi sifatida scan JavaScript'da ketma-ket va oqimga asoslangan ma'lumotlarni qanday qayta ishlashimizni inqilob qilishi kutilmoqda. Bu reduce'ga funksional, nafis muqobil bo'lib, operatsiyaning to'liq tarixini taqdim etadi.
Ushbu keng qamrovli qo'llanma sizni scan metodini chuqur o'rganishga olib boradi. Biz u hal qiladigan muammolarni, uning sintaksisini, oddiy joriy yig'indilardan murakkab holatni boshqarishgacha bo'lgan kuchli qo'llanilish holatlarini va zamonaviy, xotirani tejaydigan JavaScript ekotizimiga qanday mos kelishini o'rganamiz.
Tanish muammo: reduce'ning cheklovlari
scan nima olib kelishini chinakamiga qadrlash uchun, avvalo, keng tarqalgan stsenariyni ko'rib chiqaylik. Tasavvur qiling, sizda moliyaviy tranzaktsiyalar oqimi bor va har bir tranzaktsiyadan keyin joriy balansni hisoblashingiz kerak. Ma'lumotlar shunday ko'rinishi mumkin:
const transactions = [100, -20, 50, -10, 75]; // Depozitlar va yechib olishlar
Agar sizga faqat yakuniy balans kerak bo'lsa, Array.prototype.reduce eng zo'r vositadir:
const finalBalance = transactions.reduce((balance, transaction) => balance + transaction, 0);
console.log(finalBalance); // Natija: 195
Bu qisqa va samarali. Ammo agar siz hisob balansini vaqt o'tishi bilan diagrammada chizishingiz kerak bo'lsa-chi? Sizga har bir tranzaktsiyadan keyingi balans kerak bo'ladi: [100, 80, 130, 120, 195]. reduce metodi bu oraliq qadamlarni bizdan yashiradi; u faqat yakuniy natijani taqdim etadi.
Xo'sh, buni an'anaviy usulda qanday hal qilardik? Biz, ehtimol, tashqi holat o'zgaruvchisiga ega bo'lgan qo'lda yozilgan siklga qaytardik:
const transactions = [100, -20, 50, -10, 75];
const runningBalances = [];
let currentBalance = 0;
for (const transaction of transactions) {
currentBalance += transaction;
runningBalances.push(currentBalance);
}
console.log(runningBalances); // Natija: [100, 80, 130, 120, 195]
Bu ishlaydi, ammo uning bir nechta kamchiliklari bor:
- Imperativ uslub: Bu kamroq deklarativ. Biz holatni (
currentBalance) va natijalar to'plamini (runningBalances) qo'lda boshqaryapmiz. - Holatli va ko'p so'zli: Bu sikldan tashqarida o'zgaruvchan o'zgaruvchilarni boshqarishni talab qiladi, bu esa murakkabroq stsenariylarda kognitiv yukni va xatolar ehtimolini oshirishi mumkin.
- Kompozitsiyalanmaydi: Bu toza, zanjirlanadigan operatsiya emas. U funksional metodlar zanjiri oqimini (
map,filterva hokazo) buzadi.
Aynan shu muammoni Iterator scan yordamchisi nafislik va kuch bilan hal qilish uchun yaratilgan.
Yangi paradigma: Iterator Helpers taklifi
scan'ga to'g'ridan-to'g'ri o'tishdan oldin, u mavjud bo'lgan kontekstni tushunish muhimdir. Iterator Helpers taklifi iteratorlarni ma'lumotlarni qayta ishlash uchun JavaScript'da birinchi darajali vositaga aylantirishni maqsad qilgan. Iteratorlar JavaScript'dagi fundamental tushunchadir — ular for...of sikllari, spread sintaksisi (...) va generatorlar ortidagi mexanizmdir.
Ushbu taklif Iterator.prototype'ga to'g'ridan-to'g'ri tanish, massivga o'xshash metodlar to'plamini qo'shadi, jumladan:
map(mapperFn): Iteratorning har bir elementini o'zgartiradi.filter(filterFn): Faqat sinovdan o'tgan elementlarni hosil qiladi.take(limit): Birinchi N ta elementni hosil qiladi.drop(limit): Birinchi N ta elementni o'tkazib yuboradi.flatMap(mapperFn): Har bir elementni iteratorga o'zgartiradi va natijani yassilaydi.reduce(reducer, initialValue): Iteratorni bitta qiymatga qisqartiradi.- Va, albatta,
scan(reducer, initialValue).
Bu yerdagi asosiy afzallik dangasa baholashdir. Massiv metodlaridan farqli o'laroq, ular ko'pincha xotirada yangi, oraliq massivlar yaratadi, iterator yordamchilari esa elementlarni birma-bir, talab bo'yicha qayta ishlaydi. Bu ularni juda katta yoki hatto cheksiz ma'lumotlar oqimlarini qayta ishlash uchun xotira jihatidan juda samarali qiladi.
scan metodini chuqur o'rganish
scan metodi kontseptual jihatdan reduce'ga o'xshaydi, lekin bitta yakuniy qiymatni qaytarish o'rniga, u har bir qadamda reducer funksiyasining natijasini hosil qiluvchi yangi iteratorni qaytaradi. Bu sizga to'planishning to'liq tarixini ko'rish imkonini beradi.
Sintaksis va parametrlar
Metodning imzosi tushunarli va reduce'dan foydalangan har bir kishiga tanish bo'ladi.
iterator.scan(reducer [, initialValue])
reducer(accumulator, element, index): Iteratorning har bir elementi uchun chaqiriladigan funksiya. U quyidagilarni qabul qiladi:accumulator: Reducer'ning oldingi chaqiruvidan qaytarilgan qiymat yoki agar taqdim etilgan bo'lsainitialValue.element: Manba iteratoridan qayta ishlanayotgan joriy element.index: Joriy elementning indeksi.
accumulatorsifatida ishlatiladi va ayni paytdascanhosil qiladigan qiymatdir.initialValue(ixtiyoriy): Birinchiaccumulatorsifatida foydalanish uchun boshlang'ich qiymat. Agar taqdim etilmasa, iteratorning birinchi elementi boshlang'ich qiymat sifatida ishlatiladi va iteratsiya ikkinchi elementdan boshlanadi.
U qanday ishlaydi: Qadamma-qadam
scan'ning amalda qanday ishlashini ko'rish uchun joriy balans misolimizni kuzatib boraylik. Esda tuting, scan iteratorlar ustida ishlaydi, shuning uchun avval massivimizdan iterator olishimiz kerak.
const transactions = [100, -20, 50, -10, 75];
const initialBalance = 0;
// 1. Massivdan iterator oling
const transactionIterator = transactions.values();
// 2. scan metodini qo'llang
const runningBalanceIterator = transactionIterator.scan(
(balance, transaction) => balance + transaction,
initialBalance
);
// 3. Natija - yangi iterator. Natijalarni ko'rish uchun uni massivga o'zgartirishimiz mumkin.
const runningBalances = [...runningBalanceIterator];
console.log(runningBalances); // Natija: [100, 80, 130, 120, 195]
Parda ortida nima sodir bo'ladi:
scanreducer(a, b) => a + bva0ga tenginitialValuebilan chaqiriladi.- 1-iteratsiya: Reducer
accumulator = 0(boshlang'ich qiymat) vaelement = 100bilan chaqiriladi. U100'ni qaytaradi.scan100'ni hosil qiladi. - 2-iteratsiya: Reducer
accumulator = 100(oldingi natija) vaelement = -20bilan chaqiriladi. U80'ni qaytaradi.scan80'ni hosil qiladi. - 3-iteratsiya: Reducer
accumulator = 80vaelement = 50bilan chaqiriladi. U130'ni qaytaradi.scan130'ni hosil qiladi. - 4-iteratsiya: Reducer
accumulator = 130vaelement = -10bilan chaqiriladi. U120'ni qaytaradi.scan120'ni hosil qiladi. - 5-iteratsiya: Reducer
accumulator = 120vaelement = 75bilan chaqiriladi. U195'ni qaytaradi.scan195'ni hosil qiladi.
Natija — bizga kerak bo'lgan narsaga erishishning toza, deklarativ va kompozitsiyalanadigan usuli, qo'lda yozilgan sikllar yoki tashqi holatni boshqarishsiz.
Amaliy misollar va global qo'llanilish holatlari
scan'ning kuchi oddiy joriy yig'indilardan ancha kengroqdir. Bu butun dunyo dasturchilari uchun dolzarb bo'lgan turli sohalarda qo'llanilishi mumkin bo'lgan oqimlarni qayta ishlash uchun asosiy primitivdir.
1-misol: Holatni boshqarish va hodisalarga asoslangan manba
scan'ning eng kuchli qo'llanilishlaridan biri bu Redux kabi kutubxonalarda mavjud bo'lgan naqshlarni aks ettiruvchi holatni boshqarishdir. Tasavvur qiling, sizda foydalanuvchi harakatlari yoki dastur hodisalari oqimi mavjud. Siz ushbu hodisalarni qayta ishlash va dasturingizning har bir vaqt nuqtasidagi holatini yaratish uchun scan'dan foydalanishingiz mumkin.
Keling, oshirish, kamaytirish va qayta o'rnatish harakatlariga ega oddiy hisoblagichni modellashtiraylik.
// Harakatlar oqimini simulyatsiya qiluvchi generator funksiyasi
function* actionStream() {
yield { type: 'INCREMENT' };
yield { type: 'INCREMENT' };
yield { type: 'DECREMENT', payload: 2 };
yield { type: 'UNKNOWN_ACTION' }; // E'tiborsiz qoldirilishi kerak
yield { type: 'RESET' };
yield { type: 'INCREMENT', payload: 5 };
}
// Dasturimizning boshlang'ich holati
const initialState = { count: 0 };
// Reducer funksiyasi holatning harakatlarga javoban qanday o'zgarishini belgilaydi
function stateReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + (action.payload || 1) };
case 'DECREMENT':
return { ...state, count: state.count - (action.payload || 1) };
case 'RESET':
return { count: 0 };
default:
return state; // MUHIM: Ishlov berilmagan harakatlar uchun har doim joriy holatni qaytaring
}
}
// Ilovaning holat tarixi iteratorini yaratish uchun scan'dan foydalaning
const stateHistoryIterator = actionStream().scan(stateReducer, initialState);
// Har bir holat o'zgarishini sodir bo'lganda jurnalga yozing
for (const state of stateHistoryIterator) {
console.log(state);
}
/*
Natija:
{ count: 1 }
{ count: 2 }
{ count: 0 }
{ count: 0 } // ya'ni holat UNKNOWN_ACTION tomonidan o'zgartirilmadi
{ count: 0 } // RESET'dan keyin
{ count: 5 }
*/
Bu juda kuchli. Biz holatimiz qanday rivojlanishini deklarativ ravishda belgiladik va ushbu holatning to'liq, kuzatiladigan tarixini yaratish uchun scan'dan foydalandik. Ushbu naqsh vaqt bo'ylab diskretlash, jurnallash va oldindan aytib bo'ladigan ilovalarni yaratish uchun asosiy hisoblanadi.
2-misol: Katta oqimlarda ma'lumotlarni yig'ish
Tasavvur qiling, siz katta log faylini yoki IoT sensorlaridan kelayotgan va xotiraga sig'maydigan darajada katta ma'lumotlar oqimini qayta ishlayapsiz. Iterator yordamchilari bu yerda o'z kuchini ko'rsatadi. Keling, sonlar oqimida hozirgacha ko'rilgan maksimal qiymatni kuzatib borish uchun scan'dan foydalanaylik.
// Juda katta sensor ko'rsatkichlari oqimini simulyatsiya qiluvchi generator
function* getSensorReadings() {
yield 22.5;
yield 24.1;
yield 23.8;
yield 28.3; // Yangi maksimum
yield 27.9;
yield 30.1; // Yangi maksimum
// ... yana millionlab natija berishi mumkin
}
const readingsIterator = getSensorReadings();
// Vaqt o'tishi bilan maksimal ko'rsatkichni kuzatish uchun scan'dan foydalaning
const maxReadingHistory = readingsIterator.scan((maxSoFar, currentReading) => {
return Math.max(maxSoFar, currentReading);
});
// Bu yerda initialValue'ni uzatishimiz shart emas. `scan` birinchi
// elementni (22.5) boshlang'ich maksimum sifatida ishlatadi va ikkinchi elementdan boshlaydi.
console.log([...maxReadingHistory]);
// Natija: [ 24.1, 24.1, 28.3, 28.3, 30.1 ]
Kuting, natija birinchi qarashda biroz noto'g'ri ko'rinishi mumkin. Biz boshlang'ich qiymatni taqdim etmaganimiz uchun, scan birinchi elementni (22.5) boshlang'ich akkumulyator sifatida ishlatdi va birinchi qisqartirish natijasidan hosil qilishni boshladi. Boshlang'ich qiymatni o'z ichiga olgan tarixni ko'rish uchun biz uni aniq taqdim etishimiz mumkin, masalan -Infinity bilan.
const maxReadingHistoryWithInitial = getSensorReadings().scan(
(maxSoFar, currentReading) => Math.max(maxSoFar, currentReading),
-Infinity
);
console.log([...maxReadingHistoryWithInitial]);
// Natija: [ 22.5, 24.1, 24.1, 28.3, 28.3, 30.1 ]
Bu iteratorlarning xotira samaradorligini namoyish etadi. Biz nazariy jihatdan cheksiz ma'lumotlar oqimini qayta ishlashimiz va har bir qadamda joriy maksimum qiymatni bir vaqtning o'zida xotirada bittadan ortiq qiymat saqlamasdan olishimiz mumkin.
3-misol: Murakkab mantiq uchun boshqa yordamchilar bilan zanjirlash
Iterator Helpers taklifining haqiqiy kuchi metodlarni bir-biriga zanjir qilganingizda ochiladi. Keling, murakkabroq konveyer quramiz. Tasavvur qiling, elektron tijorat hodisalari oqimi mavjud. Biz vaqt o'tishi bilan umumiy daromadni hisoblamoqchimiz, lekin faqat VIP mijozlar tomonidan amalga oshirilgan muvaffaqiyatli yakunlangan buyurtmalardan.
function* getECommerceEvents() {
yield { type: 'PAGE_VIEW', user: 'guest' };
yield { type: 'ORDER_PLACED', user: 'user123', amount: 50, isVip: false };
yield { type: 'ORDER_COMPLETED', user: 'user456', amount: 120, isVip: true };
yield { type: 'ORDER_FAILED', user: 'user789', amount: 200, isVip: true };
yield { type: 'ORDER_COMPLETED', user: 'user101', amount: 75, isVip: true };
yield { type: 'PAGE_VIEW', user: 'user456' };
yield { type: 'ORDER_COMPLETED', user: 'user123', amount: 30, isVip: false }; // VIP emas
yield { type: 'ORDER_COMPLETED', user: 'user999', amount: 250, isVip: true };
}
const revenueHistory = getECommerceEvents()
// 1. Kerakli hodisalarni filtrlash
.filter(event => event.type === 'ORDER_COMPLETED' && event.isVip)
// 2. Faqat buyurtma miqdoriga o'zgartirish
.map(event => event.amount)
// 3. Joriy yig'indini olish uchun skanerlash
.scan((total, amount) => total + amount, 0);
console.log([...revenueHistory]);
// Ma'lumotlar oqimini kuzataylik:
// - Filtrlashdan keyin: { amount: 120 }, { amount: 75 }, { amount: 250 }
// - map'dan keyin: 120, 75, 250
// - scan'dan keyin (hosil qilingan qiymatlar):
// - 0 + 120 = 120
// - 120 + 75 = 195
// - 195 + 250 = 445
// Yakuniy natija: [ 120, 195, 445 ]
Bu misol deklarativ dasturlashning ajoyib namunasidir. Kod biznes mantig'ining tavsifi kabi o'qiladi: yakunlangan VIP buyurtmalarini filtrlash, miqdorni ajratib olish va so'ngra joriy yig'indini hisoblash. Har bir qadam kattaroq, xotirani tejaydigan konveyerning kichik, qayta ishlatiladigan va sinab ko'riladigan qismidir.
scan() va reduce(): Aniq farq
Ushbu ikkita kuchli metod o'rtasidagi farqni mustahkamlash juda muhimdir. Ular reducer funksiyasini bo'lishsalar-da, ularning maqsadi va natijasi tubdan farq qiladi.
reduce()umumlashtirish haqida. U butun ketma-ketlikni bitta, yakuniy qiymat hosil qilish uchun qayta ishlaydi. Sayohat yashirin.scan()transformatsiya va kuzatish haqida. U ketma-ketlikni qayta ishlaydi va har bir qadamdagi to'plangan holatni ko'rsatadigan bir xil uzunlikdagi yangi ketma-ketlik hosil qiladi. Sayohatning o'zi natijadir.
Quyida yonma-yon taqqoslash keltirilgan:
| Xususiyat | iterator.reduce(reducer, initial) |
iterator.scan(reducer, initial) |
|---|---|---|
| Asosiy maqsad | Ketma-ketlikni bitta umumiy qiymatga qisqartirish. | Ketma-ketlikning har bir qadamida to'plangan qiymatni kuzatish. |
| Qaytariladigan qiymat | Yakuniy to'plangan natijaning bitta qiymati (agar asinxron bo'lsa, Promise). | Har bir oraliq to'plangan natijani hosil qiladigan yangi iterator. |
| Umumiy o'xshatish | Bank hisobining yakuniy balansini hisoblash. | Har bir tranzaktsiyadan keyingi balansni ko'rsatuvchi bank ko'chirmasini yaratish. |
| Qo'llanilish holati | Sonlarni yig'ish, maksimum topish, satrlarni birlashtirish. | Joriy yig'indilar, holatni boshqarish, harakatlanuvchi o'rtacha qiymatlarni hisoblash, tarixiy ma'lumotlarni kuzatish. |
Kod taqqoslanishi
const numbers = [1, 2, 3, 4].values(); // Iterator oling
// Reduce: Manzil
const sum = numbers.reduce((acc, val) => acc + val, 0);
console.log(sum); // Natija: 10
// Keyingi operatsiya uchun sizga yangi iterator kerak bo'ladi
const numbers2 = [1, 2, 3, 4].values();
// Scan: Safar
const runningSum = numbers2.scan((acc, val) => acc + val, 0);
console.log([...runningSum]); // Natija: [1, 3, 6, 10]
Iterator yordamchilaridan bugun qanday foydalanish mumkin
Ushbu yozuv paytida, Iterator Helpers taklifi TC39 jarayonida 3-bosqichda turibdi. Bu uning yakunlanishiga va ECMAScript standartining kelajakdagi versiyasiga kiritilishiga juda yaqin ekanligini anglatadi. Hozircha barcha brauzerlarda yoki Node.js muhitlarida tabiiy ravishda mavjud bo'lmasligi mumkin bo'lsa-da, uni ishlatishni boshlash uchun kutishingiz shart emas.
Siz bu kuchli xususiyatlardan bugun polifillar orqali foydalanishingiz mumkin. Eng keng tarqalgan usul bu core-js kutubxonasidan foydalanish bo'lib, u zamonaviy JavaScript xususiyatlari uchun keng qamrovli polifildir.
Undan foydalanish uchun odatda core-jsni o'rnatasiz:
npm install core-js
Va keyin dasturingizning kirish nuqtasida maxsus taklif polifilini import qilasiz:
import 'core-js/proposals/iterator-helpers';
// Endi siz .scan() va boshqa yordamchilardan foydalanishingiz mumkin!
const result = [1, 2, 3].values()
.map(x => x * 2)
.scan((a, b) => a + b, 0);
console.log([...result]); // [2, 6, 12]
Shu bilan bir qatorda, agar siz Babel kabi transpilerdan foydalanayotgan bo'lsangiz, uni 3-bosqich takliflari uchun kerakli polifillar va o'zgartirishlarni kiritish uchun sozlashingiz mumkin.
Xulosa: Ma'lumotlarning yangi davri uchun yangi vosita
JavaScript Iterator scan yordamchisi shunchaki qulay yangi metod emas; u ma'lumotlar oqimlarini qayta ishlashning yanada funksional, deklarativ va xotirani tejaydigan usuliga o'tishni anglatadi. U reduce tomonidan qoldirilgan muhim bo'shliqni to'ldiradi, dasturchilarga nafaqat yakuniy natijaga erishish, balki to'planishning butun tarixini kuzatish va unga ta'sir o'tkazish imkonini beradi.
scan va kengroq Iterator Helpers taklifini qabul qilish orqali siz quyidagi xususiyatlarga ega kod yozishingiz mumkin:
- Yanada deklarativ: Sizning kodingiz qo'lda yozilgan sikllar bilan qanday erishayotganingizdan ko'ra, nima ga erishishga harakat qilayotganingizni aniqroq ifodalaydi.
- Yanada kompozitsiyalanadigan: O'qish va tushunish oson bo'lgan murakkab ma'lumotlarni qayta ishlash konveyerlarini yaratish uchun oddiy, sof operatsiyalarni bir-biriga zanjir qiling.
- Xotira jihatidan samaraliroq: Tizimingiz xotirasini ortiqcha yuklamasdan katta yoki cheksiz ma'lumotlar to'plamlarini qayta ishlash uchun dangasa baholashdan foydalaning.
Biz ma'lumotlarga boy va reaktiv ilovalarni yaratishda davom etar ekanmiz, scan kabi vositalar ajralmas bo'lib qoladi. Bu hodisalarga asoslangan manba va oqimlarni qayta ishlash kabi murakkab naqshlarni tabiiy, nafis va samarali tarzda amalga oshirish imkonini beruvchi kuchli primitivdir. Bugundan boshlab uni o'rganishni boshlang va siz JavaScript'da ma'lumotlarni qayta ishlash kelajagiga yaxshi tayyor bo'lasiz.