[JS] cachingdecorator 에서 어떻게 Map으로 저장한 데이터가 유지될까?
Caching Decorator
function slow(x) {
// CPU 집약적인 작업이 여기에 올 수 있습니다.
alert(`slow(${x})을/를 호출함`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) { // cache에 해당 키가 있으면
return cache.get(x); // 대응하는 값을 cache에서 읽어옵니다.
}
let result = func(x); // 그렇지 않은 경우엔 func를 호출하고,
cache.set(x, result); // 그 결과를 캐싱(저장)합니다.
return result;
};
}
slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1)이 저장되었습니다.
alert( "다시 호출: " + slow(1) ); // 동일한 결과
alert( slow(2) ); // slow(2)가 저장되었습니다.
alert( "다시 호출: " + slow(2) ); // 윗줄과 동일한 결과
위 코드는 javscript.info 사이트에서 가져온 cachinDecorator의 예제이다. 위 코드에서 cachingDecorator는 function(x)을 반환한다 function(x)은 cache에 x의 결과값이 저장되어있으면 해당 결과값을 바로 반환하고 그 외의 경우 cachingDecorator가 받은 func함수에 인자로 넘긴 결과값을 반환한다.
클로저
클로저는 outer 함수의 변수나 파라메터에 접근하는 함수이다. 예를 들면 클로저에서 외부 함수의 i라는 변수에 접근한다고 가정해보자, 만약 i가 constant라면 큰 문제가 되지 않을 것이다. 하지만 만약 i가 변수이고 우리의 의도가 i가 변하는 것을 이용해 결과를 내는 것이라면 이 부분에서 문제가 발생할 수 있다. 아래 코드를 보자
let arr = []
for (let i = 0; i < 10; i++) {
arr[i] = function(i) {
return i * 10
}
}
console.log(arr[1]()) //100
만약 위 코드를 통해 [10, 20, 30, ..., 100]의 arr에 저장된 함수가 반환하기를 기대하고 작성했다면, 코드가 의도와는 다르게 동작함을 깨달을 것이다. 이는 앞서 말했듯이 arr에 저장된 함수는 outer 함수의 변수인 i를 참조하고 있으며 arr[1]() 호출되었을 때엔 i는 이미 루프를 모두 돌고 10이 됐기 때문이다. 이를 해결하기 위해 즉시실행 함수를 사용하기도 하는데 거기까진 설명하지 않겠다.
정리
다시 돌아와서, cachingdecorator 에서 어떻게 Map으로 저장한 데이터가 유지될까? 그 이유는 우리가 slow에 cashingDecorator함수의 리턴값을 저장했기 때문이다. 즉 slow는 아래 코드로 변경되었으며, 클로저로서 기능한다.
function(x) {
if (cache.has(x)) { // if there's such key in cache
return cache.get(x); // read the result from it
}
let result = func(x); // otherwise call func
cache.set(x, result); // and cache (remember) the result
return result;
};
이 코드는 cachingDecorator의 cache 변수를 참조하고 조건에 따라 데이터를 불러오거나 추가한다. 즉, cache는 클로저 예시코드의 i 처럼 동작한다. 클로저가 호출될때 조건에 따라 업데이트되고 가비지 컬렉션에 의해 지워지지 않는이상 유지되는 것이다.