자바스크립트 함수는 이곳저곳에 전달될 수 있고, 객체로서 프로퍼티도 가질 수 있는 탁월한 유연성을 제공한다.
이러한 장점을 살려 함수를 어떻게 효율적으로 데코레이팅할것인가에 대해 이해하자.
순수함수 : 함수에 같은 값을 전달하면 언제나 같은 결과를 반환하는 함수
단순한 개념같지만 중요한 의미가 내포되어있다. 보통 함수는 지역변수 또는 외부변수를 참조할 수 있기때문에 외부환경으로 부터 영향을 받거나 외부환경에 영향을 줄 수 있다.
그렇다면 왜 순수함수가 필요한가? 순수함수여야만 하는가?
데이터변경을 어떻게 관리할것인가?가 프로그램의 근본적인 목적이다.
데이터변경은 여러 부작용을 발생시킬수 있다는 것을 명심해야 한다.
따라서 데이터변경을 최소화하는것이 안전하고 신뢰도 높은 프로그램을 만드는 것이다.
연산시간이 다소 소요되는 순수함수가 있다면 처음 실행결과를 저장해놓고 다음 함수호출부터는 저장된 결과를 반환하도록 하면 효율적일 것이다. 이러한 기능을 캐싱기능이라 한다.
프로그램을 개발하다보면 많은 순수함수를 만들게 되는데 순수함수마다 캐싱기능을 구현하는것은 효율적이지 않을뿐더러 순수함수를 변경하는 부작용도 발생한다.
데코레이터의 목적
1. 순수함수를 변경하지 않는다.
2. 추가기능을 확장한다.
캐싱기능이 없는 순수함수
function slow(x) {
console.log(`시간이 걸리는 함수 slow(${x})을 수행하여 결과를 반환함`);
return x;
}
// 순수함수를 매개변수로 받는다.
function cachingDecorator(func) {
let cache = new Map();
// 확장된 함수를 반환한다.
return function (x) {
if (cache.has(x)) { // cache에 해당 키가 있으면
return cache.get(x);
}
let result = func(x); // func를 호출후 반환값할당
cache.set(x, result); // 그 결과를 캐싱(저장)합니다.
return result;
}
}
// 순수함수를 참조하는 변수에 확장된 함수할당한다.
slow = cachingDecorator(slow);
alert( slow(1) );
cachingDecorator함수에 slow함수를 전달하면 func매개변수가 노캐싱함수를 참조하게되어 노캐싱함수를 참조하는 변수는 2개가 된다.(slow, func)
slow = cachingDecorator(slow); 이제 slow변수는 확장된 함수를 참조하게 된다.
노캐싱함수는 func변수가 참조하고 있으므로 가비지콜렉션대상이 되지 않으며 func(x);로 호출가능하다.
데코레이터를 사용하면 발생하는 이점
이러한 데코레이팅함수가 가능한 것은 자바스크립트의 함수유연성때문에 가능한 것이다.
let worker = {
someMethod() {
return 1;
},
slow(x) {
console.log(`첫번재 호출 slow(${x})을/를 호출함`);
return x * this.someMethod();
},
};
function cachingDecorator(func) {
let cache = new Map();
return function (x) {
if (cache.has(x)) {
console.log("캐싱데이터 반환함");
return cache.get(x);
}
let result = func.call(this,x);
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow);
worker.slow(2);
worker.slow(2);
cachingDecorator(worker.slow); 객체메소드의 메모리번지값을 함수에 전달하는 것이기때문에 객체의 정보가 소실된다.
func(x);처럼 객체메소드가 호출되면 this가 window가되어 제대로 처리되지 않는다. this바인딩을 해주어야 한다.
func.call(worker,x);를 지정하면 오직 worker객체메소드에만 사용가능해지기때문에 이렇게 하면 안된다.
worker.slow = cachingDecorator(worker.slow);
반환함수가 worker에서 호출된다면 this를 사용할 수 있고 순수함수내 this도 worker로 바인딩될 것이다.
여러인수 전달하기
let worker = {
someMethod() {
return 1;
},
slow(...args) {
let val = (args.reduce((acc,cur)=>acc*cur)) * this.someMethod();
console.log(`첫번재 호출 ${val}을/를 호출함`);
return val; // (*)
},
};
function cachingDecorator(func) {
let cache = new Map();
return function (...args) {
if (cache.has(args.toString())) {
let val = cache.get(args.toString());
console.log("캐싱데이터 반환함"+val);
return cache.get(val);
}
let result = func.apply(this,args);
cache.set(args.toString(), result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow);
worker.slow(2,3);
worker.slow(2,3);
여러개의 인자를 사용할 수 있으므로 이에대한 처리를 해놓는것이 좋다.
나머지연산자와 스프레드문법을 사용하면 된다.
받을때는 나머지연산자를 사용하여 배열로 받고 전달할때는 스프레드문법으로 확장해서 전달하면 된다.
func.call(this,...args);
call메소드를 사용할때는 스프레드문법으로 사용한다.
func.apply(this,args);
apply메소드는 배열을 사용하면 된다.
자바스크립트는 apply메소드가 최적화되어 빠르다고 한다.