코드 변경 없이 캐싱 기능 추가하기
: 자주 호출되는 함수의 결과를 캐싱하여 재연산에 걸리는 시간을 줄여야함
: 함수 내부에 캐싱 관련 코드를 추가하는 대신 래퍼 함수를 만들어 캐싱 기능을 추가하면 이점이 많음
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) ); // 윗줄과 동일한 결과
- 데코레이터
: 인수로 받은 함수의 행동을 변경 시켜주는 함수
: 함수에 위 예시의 cachingDecorator를 적용하기만 하면 캐싱이 가능한 함수를
원하는 만큼 구현할 수 있기 때문에 유용하게 사용되며 코드가 간결해질 수 있음
: 재사용할 수 있으며 캐싱 로직이 분리되어 로직의 복잡성이 증가하지 않음
func.call을 사용해 컨텍스트를 지정
: 데코레이터의 경우 객체 메서드에 사용하기에 적합하지 않음
// worker.slow에 캐싱 기능을 추가해봅시다.
let worker = {
someMethod() {
return 1;
},
slow(x) {
// CPU 집약적인 작업이라 가정
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod(); // (*)
}
};
// 이전과 동일한 코드
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x); // (**)
cache.set(x, result);
return result;
};
}
alert( worker.slow(1) ); // 기존 메서드는 잘 동작합니다.
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
alert( worker.slow(2) ); // 에러 발생!, Error: Cannot read property 'someMethod' of undefined
: 래퍼가 기존 메서드 호출 결과를 전달하려 했지만 this의 컨텍스트가 사라졌기 때문에 에러가 발생
: func.call을 사용하면 this를 멸시적으로 고정해 함수를 호출할 수 있음
func.call(context, arg1, arg2, ...)
function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// call을 사용해 원하는 객체가 'this'가 되도록 합니다.
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin
function say(phrase) {
alert(this.name + ': ' + phrase);
}
let user = { name: "John" };
// this엔 user가 고정되고, "Hello"는 메서드의 첫 번째 인수가 됩니다.
say.call( user, "Hello" ); // John: Hello
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod(); // (*)
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); // 이젠 'this'가 제대로 전달됩니다.
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
alert( worker.slow(2) ); // 제대로 동작합니다.
alert( worker.slow(2) ); // 제대로 동작합니다. 다만, 원본 함수가 호출되지 않고 캐시 된 값이 출력됩니다.
여러 인수를 전달하여 cachingDecorator를 다채롭게 만들기
: 위 방법들은 인수가 여러 개인 함수에는 사용할 수 없음
: Map은 단일키만 받기 때문
- 해결방법들
: 복수 키를 지원하는 맴과 유사한 자료구조 구현 (서드파티 라이브러리 등 사용)
: 중첩 맵 사용
> max, result 쌍은 cache.set(min) / cache.get(min).get(max) 이런식으로
: 두 값을 하나로 합치기
> 해싱함수를 구현하여 유연성까지 확보
- 두 값을 하나로 합치는 방식의 다중 인수 데코레이터
: 래퍼 함수로 감싼 함수가 호출될 때 복수 인수를 넘길 수 있도록 수정
let worker = {
slow(min, max) {
alert(`slow(${min},${max})을/를 호출함`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); // (*)
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments); // (**)
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert( worker.slow(3, 5) ); // 제대로 동작합니다.
alert( "다시 호출: " + worker.slow(3, 5) ); // 동일한 결과 출력(캐시된 결과)
func.apply
: func의 this를 context로 고정해주고 유사 배열 객체인 args를 인수로 만들 수 있게 해주는 유연한 메서드
func.apply(context, args)
// 비교
func.call(context, ...args); // 전개 문법을 사용해 인수가 담긴 배열을 전달하는 것과
func.apply(context, args); // call을 사용하는 것은 동일합니다.
: apply는 오직 유사 배열 형태의 arg만 받음
: 인수가 이터러블의 형태라면 call을, 유사 배열 형태라면 apply를 사용
: 자바스크립트 엔진 내부에서는 apply를 최적화 하기 때문에 apply를 권장
- 콜 포워딩
: 컨텍스트와 함께 인수 전체를 다른 함수에 전달하는 것
let wrapper = function() {
return func.apply(this, arguments);
};
메서드 빌리기
: arr.join을 활용하여 위 캐싱 함수의 해싱 함수를 개선
function hash() {
alert( [].join.call(arguments) ); // 1,2
}
hash(1, 2);
데코레이터와 함수 프로퍼티
: 함수를 데코레이터로 감싸 대체하는 것은 대체적으로 안전하지만,
원본함수에 func.calledCount 등의 프로퍼티가 있는 경우
데코레이터를 적용한 함수에서 프로퍼티를 사용할 수 없으므로 안전하지 않음
: 함수에 프로퍼티가 있는 경우에는 사용에 주의 필요
Refference
'Programming > JavaScript' 카테고리의 다른 글
모던 자바스크립트 (객체 프로퍼티 설정) 5-1. 프로퍼티 플래그와 설명자 (0) | 2022.04.23 |
---|---|
모던 자바스크립트 (함수) 4-6. 함수 바인딩 (0) | 2022.04.23 |
모던 자바스크립트 (함수) 4-4. setTiemout과 setInterval을 이용한 호출 스케줄링 (0) | 2022.04.23 |
모던 자바스크립트 (함수) 4-3. 전역 객체 / 함수 표현식 / new Function 문법 (0) | 2022.04.23 |
모던 자바스크립트 (함수) 4-2. 변수의 유효범위와 클로저 / var (0) | 2022.04.21 |