Avastage uue JavaScripti iteraatori `scan` abilise võimsus. Õppige, kuidas see muudab voo töötlemise, olekuhalduse ja andmete agregeerimise revolutsiooniliseks, ületades `reduce`.
JavaScripti iteraatori `scan`: puuduv lüli akumuleeruva voo töötlemiseks
Kaasaegse veebiarenduse pidevalt arenevas maastikus on andmed kuningas. Me tegeleme pidevalt infovoogudega: kasutaja sündmused, reaalajas API vastused, suured andmekogumid ja palju muud. Nende andmete tõhus ja deklaratiivne töötlemine on ülimalt oluline väljakutse. Aastaid on JavaScripti arendajad lootnud võimsale Array.prototype.reduce meetodile, et destilleerida massiiv üheks väärtuseks. Aga mis siis, kui teil on vaja näha teekonda, mitte ainult sihtkohta? Mis siis, kui teil on vaja jälgida iga akumuleerimise vaheetappi?
Siin astub lavale uus ja võimas tööriist: iteraatori scan abiline. Osana TC39 iteraatori abiliste ettepanekust, mis on praegu 3. etapis, on scan valmis muutma revolutsiooniliselt seda, kuidas me JavaScriptis järjestikuste ja voopõhiste andmetega tegeleme. See on funktsionaalne ja elegantne vaste reduce'ile, mis pakub operatsiooni täielikku ajalugu.
See põhjalik juhend viib teid sügavale scan meetodisse. Me uurime probleeme, mida see lahendab, selle süntaksit, selle võimsaid kasutusjuhtumeid alates lihtsatest jooksvatest summadest kuni keeruka olekuhalduseni ning seda, kuidas see sobitub kaasaegse ja mäluefektiivse JavaScripti laiemasse ökosüsteemi.
Tuttav väljakutse: `reduce` piirid
Et tõeliselt hinnata, mida scan lauale toob, vaatame kõigepealt üle levinud stsenaariumi. Kujutage ette, et teil on finantstehingute voog ja teil on vaja arvutada jooksev saldo pärast iga tehingut. Andmed võivad välja näha sellised:
const transactions = [100, -20, 50, -10, 75]; // Sissemaksed ja väljamaksed
Kui soovite ainult lõplikku saldot, on Array.prototype.reduce ideaalne tööriist:
const finalBalance = transactions.reduce((balance, transaction) => balance + transaction, 0);
console.log(finalBalance); // Väljund: 195
See on lühike ja tõhus. Aga mis siis, kui teil on vaja kontojääki aja jooksul diagrammil kujutada? Teil on vaja saldot pärast iga tehingut: [100, 80, 130, 120, 195]. reduce meetod peidab need vaheetapid meie eest; see pakub ainult lõpptulemust.
Niisiis, kuidas me seda traditsiooniliselt lahendaksime? Tõenäoliselt langeksime tagasi käsitsi tsüklile välise oleku muutujaga:
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); // Väljund: [100, 80, 130, 120, 195]
See töötab, kuid sellel on mitmeid puudusi:
- Imperatiivne stiil: See on vähem deklaratiivne. Me haldame käsitsi olekut (
currentBalance) ja tulemuste kogumit (runningBalances). - Olekuga ja mahukas: See nõuab muutuvate muutujate haldamist väljaspool tsüklit, mis võib suurendada kognitiivset koormust ja potentsiaali vigadeks keerukamate stsenaariumite korral.
- Ei ole komponeeritav: See ei ole puhas, aheldatav operatsioon. See rikub funktsionaalse meetodi aheldamise voogu (nagu
map,filterjne).
See on täpselt probleem, mille iteraatori scan abiline on loodud lahendama elegantselt ja võimsalt.
Uus paradigma: iteraatori abiliste ettepanek
Enne kui me otse scan'i juurde hüppame, on oluline mõista konteksti, milles see elab. Iteraatori abiliste ettepaneku eesmärk on muuta iteraatorid JavaScriptis andmetöötluse jaoks esmaklassilisteks kodanikeks. Iteraatorid on JavaScriptis põhiline kontseptsioon – need on mootor for...of tsüklite, levitamise süntaksi (...) ja generaatorite taga.
Ettepanek lisab rea tuttavaid, massiivilaadseid meetodeid otse Iterator.prototype'ile, sealhulgas:
map(mapperFn): teisendab iga üksuse iteraatoris.filter(filterFn): tagastab ainult üksused, mis läbivad testi.take(limit): tagastab esimesed N üksust.drop(limit): jätab vahele esimesed N üksust.flatMap(mapperFn): kaardistab iga üksuse iteraatorile ja tasandab tulemuse.reduce(reducer, initialValue): vähendab iteraatori üheks väärtuseks.- Ja loomulikult
scan(reducer, initialValue).
Peamine eelis siin on laisk väärtustamine. Erinevalt massiivimeetoditest, mis sageli loovad mällu uusi vahemassiive, töötlevad iteraatori abilised üksusi ükshaaval, nõudmisel. See muudab need uskumatult mäluefektiivseks väga suurte või isegi lõpmatute andmevoogude käsitlemisel.
SĂĽgav sukeldumine `scan` meetodisse
scan meetod on kontseptuaalselt sarnane reduce'ile, kuid selle asemel, et tagastada üks lõplik väärtus, tagastab see uue iteraatori, mis tagastab redutseerija funktsiooni tulemuse igal sammul. See võimaldab teil näha akumuleerumise täielikku ajalugu.
SĂĽntaks ja parameetrid
Meetodi signatuur on lihtne ja tundub tuttav kõigile, kes on kasutanud reduce'i.
iterator.scan(reducer [, initialValue])
reducer(accumulator, element, index): funktsioon, mida kutsutakse iga iteraatori elemendi jaoks. See saab:accumulator: väärtus, mille tagastas redutseerija eelmine kutse, võiinitialValue, kui see on olemas.element: praegune element, mida lähteteraatorist töödeldakse.index: praeguse elemendi indeks.
accumulator'ina ja see on ka väärtus, midascantagastab.initialValue(valikuline): esialgne väärtus, mida kasutada esimeseaccumulator'ina. Kui seda ei ole, kasutatakse iteraatori esimest elementi esialgse väärtusena ja iteratsioon algab teisest elemendist.
Kuidas see töötab: samm-sammult
Jälgime oma jooksvat saldonäidet, et näha scan'i tegevuses. Pidage meeles, et scan toimib iteraatoritel, nii et kõigepealt peame hankima massiivist iteraatori.
const transactions = [100, -20, 50, -10, 75];
const initialBalance = 0;
// 1. Hankige massiivist iteraator
const transactionIterator = transactions.values();
// 2. Rakendage scan meetod
const runningBalanceIterator = transactionIterator.scan(
(balance, transaction) => balance + transaction,
initialBalance
);
// 3. Tulemuseks on uus iteraator. Me saame selle tulemuste nägemiseks massiiviks teisendada.
const runningBalances = [...runningBalanceIterator];
console.log(runningBalances); // Väljund: [100, 80, 130, 120, 195]
Siin on, mis toimub kapoti all:
scankutsutakse redutseerijaga(a, b) => a + bjainitialValue'ga0.- Iteratsioon 1: Redutseerijat kutsutakse
accumulator = 0(esialgne väärtus) jaelement = 100. See tagastab100.scantagastab100. - Iteratsioon 2: Redutseerijat kutsutakse
accumulator = 100(eelmine tulemus) jaelement = -20. See tagastab80.scantagastab80. - Iteratsioon 3: Redutseerijat kutsutakse
accumulator = 80jaelement = 50. See tagastab130.scantagastab130. - Iteratsioon 4: Redutseerijat kutsutakse
accumulator = 130jaelement = -10. See tagastab120.scantagastab120. - Iteratsioon 5: Redutseerijat kutsutakse
accumulator = 120jaelement = 75. See tagastab195.scantagastab195.
Tulemuseks on puhas, deklaratiivne ja komponeeritav viis saavutada täpselt seda, mida me vajasime, ilma käsitsi tsüklite või välise olekuhalduseta.
Praktilised näited ja globaalsed kasutusjuhtumid
scan'i võimsus ulatub kaugemale lihtsatest jooksvatest summadest. See on voo töötlemise põhiline primitiiv, mida saab rakendada paljudele arendajate jaoks olulistele domeenidele.
Näide 1: Olekuhaldus ja sündmuste hankimine
Üks võimsamaid scan'i rakendusi on olekuhaldus, mis peegeldab mustreid, mida leidub sellistes teekides nagu Redux. Kujutage ette, et teil on kasutaja tegevuste või rakenduse sündmuste voog. Saate kasutada scan'i nende sündmuste töötlemiseks ja rakenduse oleku loomiseks igal ajahetkel.
Modelleerime lihtsat loendurit suurendamise, vähendamise ja lähtestamise tegevustega.
// Generaatori funktsioon tegevuste voo simuleerimiseks
function* actionStream() {
yield { type: 'INCREMENT' };
yield { type: 'INCREMENT' };
yield { type: 'DECREMENT', payload: 2 };
yield { type: 'UNKNOWN_ACTION' }; // Tuleks ignoreerida
yield { type: 'RESET' };
yield { type: 'INCREMENT', payload: 5 };
}
// Meie rakenduse esialgne olek
const initialState = { count: 0 };
// Redutseerija funktsioon määratleb, kuidas olek muutub vastuseks tegevustele
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; // OLULINE: tagastage alati praegune olek käsitlemata tegevuste jaoks
}
}
// Kasutage scan'i, et luua rakenduse olekuajaloo iteraator
const stateHistoryIterator = actionStream().scan(stateReducer, initialState);
// Logige iga olekumuutust selle toimumisel
for (const state of stateHistoryIterator) {
console.log(state);
}
/*
Väljund:
{ count: 1 }
{ count: 2 }
{ count: 0 }
{ count: 0 } // a.k.a olekut ei muudetud UNKNOWN_ACTION abil
{ count: 0 } // pärast RESET
{ count: 5 }
*/
See on uskumatult võimas. Me oleme deklaratiivselt määratlenud, kuidas meie olek areneb, ja kasutanud scan'i, et luua selle oleku täielik ja jälgitav ajalugu. See muster on ajas rändamise silumise, logimise ja ennustatavate rakenduste loomise jaoks fundamentaalne.
Näide 2: Andmete agregeerimine suurtel voogudel
Kujutage ette, et te töötlete massiivset logifaili või andmevoogu IoT sensoritest, mis on liiga suured, et mällu mahtuda. Iteraatori abilised säravad siin. Kasutame scan'i, et jälgida suurimat väärtust, mida on voos seni nähtud.
// Generaator väga suure sensorinäitude voo simuleerimiseks
function* getSensorReadings() {
yield 22.5;
yield 24.1;
yield 23.8;
yield 28.3; // Uus max
yield 27.9;
yield 30.1; // Uus max
// ... võiks tagastada miljoneid rohkem
}
const readingsIterator = getSensorReadings();
// Kasutage scan'i, et jälgida maksimaalset näitu aja jooksul
const maxReadingHistory = readingsIterator.scan((maxSoFar, currentReading) => {
return Math.max(maxSoFar, currentReading);
});
// Me ei pea siin esialgset väärtust edastama. `scan` kasutab esimest
// elementi (22.5) esialgse maksimumina ja alustab teisest elemendist.
console.log([...maxReadingHistory]);
// Väljund: [ 24.1, 24.1, 28.3, 28.3, 30.1 ]
Oodake, väljund võib esmapilgul veidi paigast ära tunduda. Kuna me ei esitanud esialgset väärtust, kasutas scan esimest üksust (22.5) esialgse akumulaatorina ja hakkas tagastama esimese redutseerimise tulemust. Et näha ajalugu, sealhulgas esialgset väärtust, saame selle selgesõnaliselt esitada, näiteks -Infinity'ga.
const maxReadingHistoryWithInitial = getSensorReadings().scan(
(maxSoFar, currentReading) => Math.max(maxSoFar, currentReading),
-Infinity
);
console.log([...maxReadingHistoryWithInitial]);
// Väljund: [ 22.5, 24.1, 24.1, 28.3, 28.3, 30.1 ]
See demonstreerib iteraatorite mäluefektiivsust. Me saame töödelda teoreetiliselt lõpmatut andmevoogu ja saada jooksvat maksimumi igal sammul, ilma et meil oleks korraga mälus rohkem kui üks väärtus.
Näide 3: Aheldamine teiste abilistega keeruka loogika jaoks
Iteraatori abiliste ettepaneku tõeline jõud avaneb siis, kui hakkate meetodeid omavahel aheldama. Ehitame keerukama torujuhtme. Kujutage ette e-kaubanduse sündmuste voogu. Me tahame arvutada kogutulu aja jooksul, kuid ainult edukalt lõpule viidud tellimustelt, mis on esitatud VIP klientide poolt.
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 }; // Mitte VIP
yield { type: 'ORDER_COMPLETED', user: 'user999', amount: 250, isVip: true };
}
const revenueHistory = getECommerceEvents()
// 1. Filtreerige õigete sündmuste jaoks
.filter(event => event.type === 'ORDER_COMPLETED' && event.isVip)
// 2. Kaardistage ainult tellimuse summa juurde
.map(event => event.amount)
// 3. Kasutage scan'i, et saada jooksev summa
.scan((total, amount) => total + amount, 0);
console.log([...revenueHistory]);
// Jälgime andmevoogu:
// - Pärast filtrit: { amount: 120 }, { amount: 75 }, { amount: 250 }
// - Pärast kaardistamist: 120, 75, 250
// - Pärast scan'i (tagastatud väärtused):
// - 0 + 120 = 120
// - 120 + 75 = 195
// - 195 + 250 = 445
// Lõplik väljund: [ 120, 195, 445 ]
See näide on ilus demonstratsioon deklaratiivsest programmeerimisest. Kood loeb nagu äri loogika kirjeldus: filtreerige lõpule viidud VIP tellimuste jaoks, eraldage summa ja seejärel arvutage jooksev summa. Iga samm on väike, taaskasutatav ja testitav osa suuremast, mäluefektiivsest torujuhtmest.
`scan()` vs. `reduce()`: selge eristus
On ülioluline kinnitada nende kahe võimsa meetodi erinevust. Kuigi neil on ühine redutseerija funktsioon, on nende eesmärk ja väljund põhimõtteliselt erinevad.
reduce()räägib summeerimisest. See töötleb tervet järjestust, et saada üks lõplik väärtus. Teekond on peidetud.scan()räägib teisendamisest ja jälgimisest. See töötleb järjestust ja loob uue sama pikkusega järjestuse, näidates akumuleeritud olekut igal sammul. Teekond on tulemus.
Siin on kõrvuti võrdlus:
| Funktsioon | iterator.reduce(reducer, initial) |
iterator.scan(reducer, initial) |
|---|---|---|
| Peamine eesmärk | Järjestuse destilleerimine üheks summeerivaks väärtuseks. | Kumuleeritud väärtuse jälgimine järjestuse igal sammul. |
| Tagastusväärtus | Üks väärtus (lubadus, kui asünkroonne) lõplikust akumuleeritud tulemusest. | Uus iteraator, mis tagastab iga vahepealse akumuleeritud tulemuse. |
| Levinud analoogia | Pangaarve lõpliku saldo arvutamine. | Pangakonto väljavõtte genereerimine, mis näitab saldot pärast iga tehingut. |
| Kasutusjuhtum | Numbrite summeerimine, maksimumi leidmine, stringide ühendamine. | Jooksvad summad, olekuhaldus, liikuvate keskmiste arvutamine, ajalooliste andmete jälgimine. |
Koodi võrdlus
const numbers = [1, 2, 3, 4].values(); // Hankige iteraator
// Reduce: sihtkoht
const sum = numbers.reduce((acc, val) => acc + val, 0);
console.log(sum); // Väljund: 10
// Te vajate järgmise toimingu jaoks uut iteraatorit
const numbers2 = [1, 2, 3, 4].values();
// Scan: teekond
const runningSum = numbers2.scan((acc, val) => acc + val, 0);
console.log([...runningSum]); // Väljund: [1, 3, 6, 10]
Kuidas Iteraatori abilisi täna kasutada
Selle kirjutamise ajal on Iteraatori abiliste ettepanek TC39 protsessis 3. etapis. See tähendab, et see on väga lähedal lõplikule vormistamisele ja lisamisele tulevasesse ECMAScripti standardi versiooni. Kuigi see ei pruugi olla veel kõigis brauserites või Node.js keskkondades algselt saadaval, ei pea te selle kasutamise alustamiseks ootama.
Saate neid võimsaid funktsioone täna polütäidiste kaudu kasutada. Kõige tavalisem viis on kasutada core-js teeki, mis on kaasaegsete JavaScripti funktsioonide jaoks põhjalik polütäidis.
Selle kasutamiseks peaksite tavaliselt installima core-js:
npm install core-js
Ja seejärel importige rakenduse sisenemispunktis konkreetne ettepaneku polütäidis:
import 'core-js/proposals/iterator-helpers';
// NĂĽĂĽd saate kasutada .scan() ja teisi abilisi!
const result = [1, 2, 3].values()
.map(x => x * 2)
.scan((a, b) => a + b, 0);
console.log([...result]); // [2, 6, 12]
Alternatiivselt, kui kasutate transpilerit nagu Babel, saate seda konfigureerida nii, et see sisaldaks vajalikke polütäidiseid ja teisendusi 3. etapi ettepanekute jaoks.
Järeldus: uus tööriist uueks andmete ajastuks
JavaScripti iteraatori scan abiline on rohkem kui lihtsalt mugav uus meetod; see kujutab endast nihet funktsionaalsema, deklaratiivsema ja mäluefektiivsema viisi poole andmevoogude käsitlemiseks. See täidab kriitilise lünga, mille reduce jättis, võimaldades arendajatel mitte ainult jõuda lõpptulemuseni, vaid ka jälgida ja tegutseda kogu akumuleerumise ajaloo põhjal.
Kasutades scan'i ja laiemat Iteraatori abiliste ettepanekut, saate kirjutada koodi, mis on:
- Deklaratiivsem: Teie kood väljendab selgemalt mida te saavutada üritate, mitte kuidas te seda saavutate käsitsi tsüklitega.
- Komponeeritavam: Aheldage lihtsaid, puhtaid toiminguid, et ehitada keerukaid andmetöötlustorujuhtmeid, mida on lihtne lugeda ja mõista.
- Mäluefektiivsem: Kasutage laiska väärtustamist, et töödelda massiivseid või lõpmatuid andmekogumeid ilma oma süsteemi mälu üle koormamata.
Kuna me ehitame jätkuvalt rohkem andmemahukaid ja reaktiivseid rakendusi, muutuvad sellised tööriistad nagu scan hädavajalikuks. See on võimas primitiiv, mis võimaldab keerukaid mustreid, nagu sündmuste hankimine ja voo töötlemine, rakendada algselt, elegantselt ja tõhusalt. Alustage selle uurimist juba täna ja te olete hästi ette valmistatud andmete käsitlemise tulevikuks JavaScriptis.