Kaip naudoti „Memoize“, norint išsaugoti „JavaScript“ funkcijos rezultatus ir paspartinti kodą

Funkcijos yra neatsiejama programavimo dalis. Jie padeda mūsų kodui pridėti moduliškumą ir pakartotinį naudojimą .

Gana įprasta mūsų programą suskirstyti į dalis, naudojant funkcijas, kuriomis vėliau galime paskambinti, kad atliktume naudingus veiksmus.

Kartais funkcijai gali tekti brangiai skambinti kelis kartus (tarkim, funkcijai apskaičiuoti faktoriaus skaičių). Tačiau yra būdas, kuriuo galime optimizuoti tokias funkcijas ir priversti jas vykdyti daug greičiau: talpyklą .

Pavyzdžiui, tarkime, kad turime functiongrąžinti skaičiaus faktorialą:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

Puiku, dabar rasime factorial(50). Kompiuteris atliks skaičiavimus ir pateiks mums galutinį atsakymą, mielas!

Kai tai bus padaryta, rasime factorial(51). Kompiuteris vėl atlieka daugybę skaičiavimų ir gauna mums rezultatą, tačiau galbūt pastebėjote, kad mes jau kartojame kelis veiksmus, kurių buvo galima išvengti. Optimizuotas būdas būtų:

factorial(51) = factorial(50) * 51

Bet functionkiekvieną kartą, kai jis vadinamas, skaičiavimus atliekame nuo nulio:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Ar nebūtų šaunu, jei kažkaip mūsų factorialfunkcija galėtų prisiminti ankstesnių skaičiavimų reikšmes ir naudoti jas vykdymui paspartinti?

Ateina atmintinė , būdas mums functionprisiminti (talpyklą) rezultatus. Dabar, kai jau suprantate, ką mes stengiamės pasiekti, pateikiame oficialų apibrėžimą:

Atmintinė yra optimizavimo technika, pirmiausia naudojama kompiuterių programoms pagreitinti, saugant brangių funkcijų iškvietimų rezultatus ir grąžinant talpykloje esantį rezultatą, kai vėl atsiranda tie patys įėjimai.

Įsiminti paprastais žodžiais reiškia įsiminti ar išsaugoti atmintyje. Įrašyta funkcija paprastai yra greitesnė, nes jei funkcija paskambinta vėliau su ankstesne (-ėmis) verte (-omis), tada užuot vykdę funkciją, mes gautume rezultatą iš talpyklos.

Štai kaip gali atrodyti paprasta atmintyje išsaugota funkcija (o štai „CodePen“, jei norite su ja bendrauti) :

// 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

Išsinešimas iš atminties

Keletas aukščiau nurodyto kodo yra:

  • memoizedAddgrąžina a, functionkuris yra iškviečiamas vėliau. Tai įmanoma, nes „JavaScript“ funkcijos yra pirmos klasės objektai, leidžiantys jas naudoti kaip aukštesnės eilės funkcijas ir grąžinti kitą funkciją.
  • cachegali prisiminti savo vertes, nes grąžinama funkcija yra uždaryta.
  • Labai svarbu, kad atmintyje įrašyta funkcija būtų gryna. Gryna funkcija grąžins tą pačią išvestį konkrečiam įėjimui, nesvarbu, kiek kartų ji bus vadinama, todėl cachedarbas bus toks, kokio tikėtasi.

Savo memoizefunkcijos rašymas

Ankstesnis kodas veikia gerai, bet ką daryti, jei mes norėtume bet kurią funkciją paversti atmintinė funkcija?

Štai kaip parašyti savo atminties funkciją (kodepen):

// 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

Dabar tai puiku! Ši paprasta memoizefunkcija functionsupakuos bet kokį paprastą elementą į atmintį. Kodas puikiai veikia paprastoms funkcijoms ir jį galima lengvai pritaikyti tvarkant bet kokį skaičių argumentspagal jūsų poreikius. Kita alternatyva yra pasinaudoti kai kuriomis de facto bibliotekomis, tokiomis kaip:

  • Lodashas _.memoize(func, [resolver])
  • ES7 @memoizedekoratoriai iš dekko

Rekurzinių funkcijų įsiminimas

Jei bandysite perkelti rekursyvią funkciją į funkciją, esančią memoizeaukščiau arba _.memoizeiš „Lodash“, rezultatai nebus tokie, kokių tikėtasi, nes rekursyvioji jo vėlesnių skambučių funkcija paskambins pati, o ne į atmintinę įrašyta funkcija, todėl nenaudos cache.

Tiesiog įsitikinkite, kad jūsų rekursinė funkcija iškviečia atminties funkciją. Štai kaip galite pakoreguoti vadovėlio faktinį pavyzdį („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

Keli dalykai, kuriuos reikia atkreipti dėmesį į šį kodą:

  • factorialFunkcija rekursyviai paskambinus memoized versiją savaime.
  • Užrašyta funkcija talpina ankstesnių faktorių vertes, o tai žymiai pagerina skaičiavimus, nes jas galima naudoti pakartotinai factorial(6) = 6 * factorial(5)

Ar atmintinė yra tokia pati kaip talpykla?

Taip, savotiškai. Atmintinė iš tikrųjų yra specifinė talpyklos rūšis. Nors spartinimo galite kreiptis apskritai bet saugojimo techniką (pavyzdžiui, HTTP spartinimo) naudoti ateityje, memoizing specialiai apima spartinimo grąžinimo vertybės, function.

Kada atminti savo funkcijas

Nors gali atrodyti, kad atmintinę galima naudoti su visomis funkcijomis, ji iš tikrųjų turi ribotus naudojimo atvejus:

  • Norint atminti funkciją, ji turėtų būti gryna, kad tų pačių įėjimų kaskart grąžinimo vertės būtų vienodos
  • Įsiminimas yra kompromisas tarp pridėtos vietos ir padidinto greičio, todėl reikšmingas tik funkcijoms, kurių įvesties diapazonas yra ribotas, kad talpyklos reikšmes būtų galima naudoti dažniau
  • Gali atrodyti, kad turėtumėte užrašyti API skambučius, tačiau tai nėra būtina, nes naršyklė juos automatiškai talpina už jus. Norėdami sužinoti daugiau, žr. HTTP talpyklą
  • Geriausias mano pastebėtų funkcijų naudojimo atvejis yra sunkiosios skaičiavimo funkcijos, kurios gali žymiai pagerinti našumą (faktorialas ir fibonači nėra tikrai geri realaus pasaulio pavyzdžiai).
  • Jei naudojate „React / Redux“, galite patikrinti pakartotinį pasirinkimą, kuris naudoja pažymėtą atmintinę, kad įsitikintumėte, jog skaičiavimai atliekami tik tada, kai pasikeičia susijusioje būsenos medžio dalyje.

Papildoma literatūra

Šios nuorodos gali būti naudingos, jei norėtumėte daugiau sužinoti apie kai kurias šio straipsnio temas:

  • Aukštesnės užsakymo funkcijos „JavaScript“
  • Uždarymai „JavaScript“
  • Grynos funkcijos
  • Lodasho _.memoizedokumentai ir šaltinio kodas
  • Daugiau prisiminimų pavyzdžių čia ir čia
  • reaguoti / perrinkti

Tikiuosi, kad šis straipsnis buvo jums naudingas, ir jūs geriau supratote „JavaScript“ atmintines :)

Galite sekti mane „Twitter“ ir gauti naujausių naujinių. Aš taip pat pradėjau skelbti naujesnius įrašus savo asmeniniame tinklaraštyje.