Како се користи Мемоизе за кеширање резултата ЈаваСцрипт функције и убрзавање кода

Функције су саставни део програмирања. Они помажу у додавању модуларности и поновне употребљивости нашем коду.

Уобичајено је да наш програм поделимо на делове помоћу функција које можемо позвати касније да бисмо извршили неку корисну радњу.

Понекад функција може постати скупа за позивање више пута (рецимо, функција за израчунавање факторијела броја). Али постоји начин да такве функције оптимизујемо и учинимо да се извршавају много брже: кеширање .

На пример, рецимо да морамо functionвратити факторијел броја:

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

Сјајно, сад да нађемо factorial(50). Рачунар ће извршити прорачуне и вратити нам коначни одговор, слатко!

Кад је то завршено, хајде да пронађемо factorial(51). Рачунар поново врши бројне прорачуне и даје нам резултат, али можда сте приметили да већ понављамо бројне кораке који су могли да се избегну. Оптимизирани начин био би:

factorial(51) = factorial(50) * 51

Али ми functionизвршавамо прорачуне од нуле сваки пут кад се позове:

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

Зар не би било кул кад би наша factorialфункција некако могла да памти вредности из својих претходних израчунавања и користи их да убрза извршавање?

У питању мемоизатион , начин за наше functionто Ремембер (цацхе) резултатима. Сад кад сте основно разумели шта покушавамо да постигнемо, ево формалне дефиниције:

Мемоизација је техника оптимизације која се првенствено користи за убрзавање рачунарских програма чувањем резултата скупих позива функција и враћањем кешираног резултата када се поново појаве исти улази

Меморисање једноставним речима значи памћење или чување у меморији. Меморисана функција је обично бржа, јер ако се функција позва накнадно са претходним вредностима, тада бисмо уместо извршавања функције дохваћали резултат из кеша.

Ево како би могла изгледати једноставна меморисана функција (а овде је ЦодеПен у случају да желите да комуницирате с њим) :

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

Мемоизација за понети

Неки за понети из горњег кода су:

  • memoizedAddвраћа а functionкоји се касније позива. То је могуће јер су у ЈаваСцрипт-у функције првокласни објекти што нам омогућава да их користимо као функције вишег реда и вратимо другу функцију.
  • cacheможе да памти његове вредности јер функција која се враћа има затварање над собом.
  • Неопходно је да меморисана функција буде чиста. Чиста функција ће вратити исти излаз за одређени улаз без обзира колико је пута позван, што чини cacheрад очекиваним.

Писање сопствене memoizeфункције

Претходни код добро функционише, али шта ако бисмо желели било коју функцију претворити у меморирану функцију?

Ево како да напишете сопствену функцију памћења (цодепен):

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

Сад је то сјајно! Ова једноставна memoizeфункција functionумотаће било шта једноставно у запамћени еквивалент. Код добро функционише за једноставне функције и лако га можете подесити да обрађује било који број argumentsпрема вашим потребама. Друга алтернатива је коришћење неких фактичких библиотека као што су:

  • Лодасх-а _.memoize(func, [resolver])
  • ЕС7 @memoizeдекоратори од децко

Меморисање рекурзивних функција

Ако покушате да рекурзивну функцију пренесете на memoizeфункцију изнад или _.memoizeиз Лодасх-а, резултати неће бити онакви какви су се очекивали, јер ће рекурзивна функција у својим наредним позивима на крају позвати себе уместо меморисане функције, чиме се неће користити cache.

Само се уверите да рекурзивна функција позива меморисану функцију. Ево како можете да подесите факторски пример уџбеника (цодепен):

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

Неколико напомена из овог кода:

  • factorialФункција рекурзивно позивање мемоизед верзију себе.
  • Меморисана функција кешира вредности претходних фактора, што значајно побољшава прорачуне јер се могу поново користити factorial(6) = 6 * factorial(5)

Да ли је меморисање исто што и кеширање?

Да, некако. Мемоизација је заправо посебна врста кеширања. Иако се кеширање може генерално односити на било коју технику складиштења (попут ХТТП кеширања) за будућу употребу, меморисање посебно укључује кеширање повратних вредности а function.

Када памтити своје функције

Иако може изгледати да се памћење може користити са свим функцијама, у ствари има ограничене случајеве употребе:

  • Да би меморисао функцију, требало би да буде чиста тако да повратне вредности сваки пут буду исте за исте улазе
  • Меморисање је компромис између додатог простора и додане брзине и стога је значајно само за функције које имају ограничен опсег уноса, тако да се кеширане вредности могу чешће користити
  • Могло би изгледати као да бисте требали запамтити АПИ позиве, али то није потребно јер их прегледач аутоматски кешира за вас. Погледајте ХТТП предмеморирање за више детаља
  • Најбољи случај примене за меморисане функције је за тешке рачунске функције које могу значајно побољшати перформансе (факторијели и фибоначи нису заиста добри примери из стварног света)
  • Ако се бавите Реацт / Редук-ом, можете да изаберете реселецт који користи меморисани селектор како би осигурао да се прорачуни дешавају само када се промена догоди у повезаном делу стабла стања.

Додатна литература

Следеће везе могу бити корисне ако желите детаљније да знате више о неким темама из овог чланка:

  • Функције вишег реда у ЈаваСцрипт-у
  • Затварање у ЈаваСцрипт-у
  • Чисте функције
  • Лодасх-ови _.memoizeдокументи и изворни код
  • Још примера мемоизације овде и овде
  • реаговати / поново одабрати

Надам се да вам је овај чланак био користан и да сте стекли боље разумевање памћења у ЈаваСцрипт-у :)

Можете ме пратити на твиттеру ради најновијих ажурирања. Такође сам почео да објављујем новије објаве на свом личном блогу.