๋ฐ˜์‘ํ˜•

_.memoize ์†Œ์Šค์ฝ”๋“œ


Lodash ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ Memoize ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด์ „์— ์ง„ํ–‰ํ–ˆ๋˜ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์ฐฝ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์ž…๋ ฅํ•œ ํ‚ค์›Œ๋“œ์— ๋Œ€ํ•œ API ํ˜ธ์ถœ์„ ์‹œ๋„ํ•˜๋Š”๋ฐ, ์ด๋ฏธ ๊ฒ€์ƒ‰ํ–ˆ๋˜ ํ‚ค์›Œ๋“œ๋Š” ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ด๋†“๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋ฉด API ์ค‘๋ณต ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ Lodash์˜ Memoize๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค(๋ฌผ๋ก  ํ‚ค์›Œ๋“œ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ’์ด ์ž์ฃผ ๋ณ€ํ•œ๋‹ค๋ฉด ์บ์‹ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ํ•„์š” ์—†๋‹ค)

 

์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์ฐฝ์— ํ™œ์šฉํ•œ Memoize

import _ from 'lodash';

export const requestQuotes = _.memoize(async (title) => {
  const res = await fetch(
    `https://animechan.vercel.app/api/quotes/anime?title=${title}`,
  );
  if (res.status !== 200) return [];

  const quotesArray = await res.json();
  return quotesArray;
});

requestQuotes('china'); // 'china' ํ‚ค์›Œ๋“œ์— ๋Œ€ํ•œ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ ์บ์‹ฑ(Map์˜ 'china' key์— ์ €์žฅ)
requestQuotes.cache; // Map(1) { 'china' -> ...} ๋Œ€์ถฉ ์ด๋Ÿฐ ๋ชจ์–‘

 

Memoize ๋ฉ”์„œ๋“œ์˜ ์บ์‹ฑ์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์–ด๋””์— ์ €์žฅ๋˜๋Š”์ง€ ๊ถ๊ธˆํ•ด์„œ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ฐพ์•„๋ดค๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ Map๊ณผ ํด๋กœ์ €๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค. memoize ํ•จ์ˆ˜์˜ ์ฒซ๋ฒˆ์งธ ์ฝœ๋ฐฑ์ด ๋ฐ›๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” Map(์บ์‹œ)์˜ key๋กœ ์‚ฌ์šฉ๋œ๋‹ค. Map์€ ๋ฌธ์ž, ์ˆซ์ž ๊ฐ™์€ ์›์‹œํ˜•์€ ๋ฌผ๋ก  ์ฐธ์กฐํ˜•(ํ•จ์ˆ˜, ๊ฐ์ฒด ๋“ฑ)๋„ key๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Map์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

 

_.memoize ์†Œ์Šค ์ฝ”๋“œ โ–ผ

function memoize(func, resolver) {
  if (
    typeof func !== 'function' ||
    (resolver != null && typeof resolver !== 'function')
  ) {
    throw new TypeError('Expected a function');
  }
  const memoized = function (...args) {
    const key = resolver ? resolver.apply(this, args) : args[0];
    const { cache } = memoized;

    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = func.apply(this, args);
    memoized.cache = cache.set(key, result) || cache;
    return result;
  };

  memoized.cache = new (memoize.Cache || Map)();
  return memoized;
}

memoize.Cache = Map;

 

์ฐธ๊ณ ๋กœ memoize.Cache ํ”„๋กœํผํ‹ฐ๋Š” Map ์ƒ์„ฑ์ž ํ•จ์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๋„๋ก ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค.

memoize.Cache === Map; // true

 

์ž‘๋™ ๋ฐฉ์‹


์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ํ•จ์ˆ˜๋Š” ์ผ๊ธ‰ ๊ฐ์ฒด์ด๋ฏ€๋กœ, ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€/์‚ญ์ œ ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ length(ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐฏ์ˆ˜)์™€ prototype(์ƒ์„ฑ์ž ํ•จ์ˆ˜์ผ ๋•Œ) ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„๋‹ค. ์ต๋ช…ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ฉด name ํ”„๋กœํผํ‹ฐ๋„ ๊ฐ€์ง„๋‹ค.

const add10 = (num) => num + 10;
Object.getOwnPropertyDescriptors(add10);
// length: {value: 1, writable: false, enumerable: false, configurable: true}
// name: {value: 'add10', writable: false, enumerable: false, configurable: true}

add10.desc = '์ˆซ์ž๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ 10์„ ๋”ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜';
console.log(add10.desc); // '์ˆซ์ž๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ 10์„ ๋”ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜'

delete add10.desc; // true
console.log(add10.desc); // undefined

 

๐Ÿ’ก memoize() ํŒŒ๋ผ๋ฏธํ„ฐ์— resolver ํ•จ์ˆ˜๋ฅผ ๋„˜๊ธฐ๋ฉด, resolver๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์„ ์บ์‹œ์˜ key๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

 

์ˆซ์ž๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ 10์„ ๋”ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” add10 ํ•จ์ˆ˜๋ฅผ memoize์˜ ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธด๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณธ๋‹ค. memoize ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋Š” cache(Map ๊ฐ์ฒด)๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์—ฌ๊ธฐ์— ์บ์‹ฑ๋  ๊ฐ’๋“ค์ด ๋‹ด๊ธด๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒผ๋˜ add10 ํ•จ์ˆ˜๋Š” memoize ํ•จ์ˆ˜์˜ ์ง€์—ญ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋ก๋ผ์„œ ์‚ฌ์šฉ๋œ๋‹ค(ํด๋กœ์ €).

const add10 = (num) => num + 10;
const cachedValue = memoize(add10);

console.log(cachedValue); // ƒ (...args) {...}
console.log(cached.cache); // Map(0) {size: 0}

 

cachedValue ํŒŒ๋ผ๋ฏธํ„ฐ์— 8์„ ๋„˜๊ฒจ ์‹คํ–‰ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด...

cachedValue(8);

 

โถ key ํš๋“. ์ œ๊ณต๋œ resolver ํ•จ์ˆ˜๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์€ 8์ด key๊ฐ€ ๋œ๋‹ค. (args๋Š” [8])

 

โท cache(Map) ๊ฐ์ฒด์— 8์ด๋ผ๋Š” key๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ. ์žˆ์œผ๋ฉด ํ•ด๋‹น key(8)์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์—†์œผ๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„ ์ง„ํ–‰.

 

โธ memoize ํ•จ์ˆ˜๊ฐ€ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฐ›์•˜๋˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜(add10)์— 8์„ ๋„˜๊ฒจ์„œ ์‹คํ–‰. apply ๋ฉ”์„œ๋“œ๋Š” ๋‘๋ฒˆ์งธ ์ธ์ž์— ๊ฐ’์„ ๋„˜๊ธธ๋•Œ๋Š” ๋ฐฐ์—ด ํ˜•ํƒœ์ด๊ณ , ๋ฐ›๋Š” ๊ณณ์—์„  ๋ฐฐ์—ด์ด ํ’€๋ฆฐ ํ˜•ํƒœ์ด๋‹ค.

const result = func.apply(this, args); // func = add10, args = [8]
// apply ์ฐธ๊ณ : Math.max.apply(null, [3, 2, 5]) -> Math.max(...[3, 2, 5])์™€ ๋™์ผ

 

โน ์‹คํ–‰ ๊ฒฐ๊ณผ cache(Map ๊ฐ์ฒด)์— ์ €์žฅ. Map์˜ set ๋ฉ”์„œ๋“œ๋Š” value ์ €์žฅ ํ›„ ํ•ญ์ƒ ์ž๊ธฐ ์ž์‹ (Map)์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

โบ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜.

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•