A függvények a programozás szerves részét képezik. Segítenek a modularitás és az újrafelhasználhatóság hozzáadásában a kódunkba .
Elég gyakori, hogy programunkat darabokra osztjuk olyan funkciók használatával, amelyeket később felhívhatunk néhány hasznos művelet végrehajtására.
Néha drágábbá válhat egy függvény többszöri meghívása (például egy szám faktoriálisának kiszámításához használt függvény). De van olyan módszer, amellyel optimalizálhatjuk az ilyen funkciókat, és sokkal gyorsabban végrehajthatjuk őket: gyorsítótárazás .
Tegyük fel például, hogy function
vissza kell adnunk egy szám faktoriálját:
function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }
Remek, most keressük meg factorial(50)
. A számítógép elvégzi a számításokat és visszaadja nekünk a végső választ, édes!
Ha ez megtörtént, keressük meg factorial(51)
. A számítógép ismét számos számítást végez, és megkapja az eredményt, de talán észrevette, hogy már számos olyan lépést megismételünk, amelyeket elkerülhetett volna. Optimalizált módon:
factorial(51) = factorial(50) * 51
De function
a számításokat a semmiből hajtjuk végre, amikor csak hívják:
factorial(51) = 51 * 50 * 49 * ... * 2 * 1
Nem lenne jó, ha valamilyen módon a factorial
funkciónk emlékezni tudna az előző számításaiból származó értékekre és felhasználná őket a végrehajtás felgyorsítására?
Jön a memoizálás , egy mód arra, function
hogy emlékezzünk (gyorsítótárba) az eredményeket. Most, hogy alaposan megértette, mit próbálunk elérni, íme egy hivatalos meghatározás:
A memorizálás egyszerű kifejezéssel azt jelenti, hogy memorizál vagy memóriában tárol. A memóriában szereplő függvény általában gyorsabb, mert ha a függvényt az előző értékkel később hívják meg, akkor a függvény végrehajtása helyett az eredményt a gyorsítótárból kapnánk.
Így nézhet ki egy egyszerű memória funkció (és itt van egy CodePen, ha kölcsönhatásba akar lépni vele) :
// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached
Memorizálás elvihetők
Néhány elvitel a fenti kódból:
memoizedAdd
a-t ad vissza,function
amelyre később hivatkozunk. Ez azért lehetséges, mert a JavaScript-ben a függvények első osztályú objektumok, amelyek lehetővé teszik, hogy magasabb rendű függvényként használjuk őket, és egy másik függvényt adjunk vissza.cache
emlékszik az értékeire, mivel a visszaküldött függvény bezáródik rajta.- Elengedhetetlen, hogy az emlékezetes funkció tiszta legyen. A tiszta függvény ugyanazt a kimenetet adja vissza egy adott bemenethez, nem számít, hányszor hívják meg, ami
cache
a vártnak megfelelő munkát eredményezi.
Saját memoize
funkció írása
Az előző kód jól működik, de mi lenne, ha bármelyik funkciót emlékezetes funkcióvá szeretnénk alakítani?
Így írhatja meg saját memoize funkcióját (codepen):
// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached
Most nagyszerű! Ez az egyszerű memoize
függvény minden egyszerűt function
memoár megfelelővé csomagol . A kód jól működik az egyszerű funkcióknál, és könnyen módosítható, hogy tetszőleges számú számot kezeljen az arguments
Ön igényeinek megfelelően. Egy másik alternatíva néhány de facto könyvtár használata, például:
- Lodash's
_.memoize(func, [resolver])
- ES7
@memoize
dekorátorok a deckótól
Rekurzív funkciók megjegyzése
Ha megpróbál rekurzív függvényt átadni a Lodash memoize
feletti vagy _.memoize
onnan érkező függvénynek, az eredmények nem lesznek a vártnak megfelelőek, mivel a későbbi hívások rekurzív függvénye a memóriás funkció helyett önmagát hívja fel, így nem használja a cache
.
Csak győződjön meg arról, hogy a rekurzív függvény meghívja az emlékeztető funkciót. Így módosíthatja a tankönyv tényleges példáját (codepen):
// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5
Néhány megjegyzendő pont ebből a kódból:
- A
factorial
függvény rekurzívan hívja önmagának memoizált változatát. - A memóriában tárolt függvény gyorsítótárba helyezi a korábbi faktoriálok értékeit, ami jelentősen javítja a számításokat, mivel újból felhasználhatók
factorial(6) = 6 * factorial(5)
A memória megegyezik a gyorsítótárral?
Igen, valahogy. A memória valójában a gyorsítótár speciális típusa. Míg a gyorsítótárazás általában bármilyen tárolási technikára utalhat (például a HTTP gyorsítótárazás) a jövőbeni felhasználáshoz, a memóriaírás kifejezetten magában foglalja az a visszatérési értékeinek gyorsítótárazásátfunction
.
Mikor kell megjegyezni a funkciókat
Bár úgy tűnhet, hogy az emlékeztető minden funkcióval használható, valójában korlátozott használati esetekkel rendelkezik:
- A függvény memóriájához annak tisztának kell lennie, hogy a visszatérési értékek minden alkalommal ugyanazok a bemenetek legyenek
- A memorizálás kompromisszum a megnövelt hely és a hozzáadott sebesség között, és így csak a korlátozott bemeneti tartományú funkcióknál fontos, így a gyorsítótárazott értékek gyakrabban használhatók
- Úgy tűnhet, hogy emlékeznie kell az API-hívásokra, de ez nem szükséges, mert a böngésző automatikusan tárolja őket az Ön számára. További részletek: HTTP gyorsítótár
- A memoizált funkciók esetében a legjobb felhasználási mód nehéz számítógépes funkciókra vonatkozik, amelyek jelentősen javíthatják a teljesítményt (a faktoriális és a fibonacci nem igazán jó példák a valós világra)
- Ha a React / Redux-ot kedveli, megnézheti az újraválasztást, amely egy jegyzetelt választót használ annak biztosítására, hogy a számítások csak akkor történjenek, amikor változás történik az állapotfa kapcsolódó részében.
További irodalom
The following links can be useful if you would like to know more about some of the topics from this article in more detail:
- Higher order functions in JavaScript
- Closures in JavaScript
- Pure functions
- Lodash’s
_.memoize
docs and source code - More memoization examples here and here
- reactjs/reselect
I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript :)
You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.