-
call/apply와 데코레이터, 포워딩프로그래밍/Js 2021. 7. 20. 11:56
https://ko.javascript.info/call-apply-decorators 내용을 요약한 것이다
코드 변경없이 캐싱 기능 추가하기
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와 같이 인수로 받은 함수의 행동을 변경시켜주는 함수를 데코레이터라고 한다.
cachingDecorator 호출할 때 반환되는 것은 캐싱 래퍼이다. 함수에 cachingDecorator를 적용하기만 하면 캐싱이 가능한 함수를 원하는 만큼 구현할 수 있다.
'func.call'를 사용해 컨텍스트 지정하기
위에서 구현한 캐싱 데코레이터는 객체 메서드에 사용하기엔 적합하지 않다.
객체 메서드 worker.slow()는 데코레이터 적용 후 제대로 동작하지 않는다.
// 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.someMethod 접근에 실패했기 때문에, Error: Cannot read property 'someMethod' of undefined 한 에러가 발생한다.
(**)로 표시한 줄에서 래퍼가 기존 함수 func(x)를 호출하면 this가 undefined 되기 때문이다.
따라서, this를 명시적으로 고정해줘야 하는데, 특별히 내장함수 메서드 func.call(context, ...args)를 사용할 수 있다.
// 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.call(this,x); cache.set(x, result); return result; }; } alert( worker.slow(1) ); // 기존 메서드는 잘 동작합니다. worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용 alert( worker.slow(2) ); //정상 작동
'func.apply' 사용하기
func.apply 문법은 다음과 같다
func.apply(context, args)
apply는 func의 this를 context로 고정해주고, 유사 배열 객체인 args를 인수로 사용할 수 있게 해준다.
call과 apply의 문법적 차이는 call이 복수 인수를 따로따로 받는 대신, apply는 인수를 유사 배열 객체로 받는다는 점이다.
- call은 인수로 iterable형태(ex) ...args(전개문법))를 받음
- apply는 인수로 유사배열(ex args)를 받음
대부분의 자바스크립트 엔진은 내부에서 apply를 최적화 하기 때문에 apply를 사용하는게 좀 더 빠르긴 하다.
이렇게 context와 함께 인수 전체를 다른 함수에 전달하는 것을 콜 포워딩(call forwarding)이라고 한다.
메서드 빌리기
다음과 같은 함수가 있다.
function hash() { return arguments.join(); //Error: arguments.join is not a function }
함수를 실행하면 에러가 발생하는데, arguments는 진짜 배열이 아니고 이터러블 객체나 유사배열이기 때문이다.
하지만 배열에서 메서드를 빌려오면 join을 사용할 수 있다.
function hash() { alert( [].join.call(arguments) ); }
*참조
'프로그래밍 > Js' 카테고리의 다른 글
var, let, const (0) 2022.03.23 new Function 문법 (0) 2021.07.05 객체로서의 함수와 기명 함수 표현식 (0) 2021.07.02 JS-객체 (0) 2021.05.12 JS - 추상관계비교 (0) 2021.02.24