- 코어 자바스크립트를 읽고, 콜백 함수에 대해 공부한 내용을 정리한다.
콜백 함수(Callback Function)
: 다른 코드의 인자로 넘겨주는 함수, 매개변수를 통해 다른 함수의 내부로 전달되는 함수.
※ 고차 함수(Higher Order Function): 매개변수를 통해 함수 외부에서 콜백 함수를 전달받은 함수.
- 콜백 함수를 받은 고차 함수는 원하는 때에 콜백 함수를 실행할 수 있음.
- 즉, 콜백 함수의 제어권을 다른 함수에 넘겨주는 것.
제어권
고차함수는 콜백 함수에 여러가지 제어권을 가질 수 있다.
1. 호출 시점
고차 함수는 콜백 함수를 자신이 원하는 시점에 실행할 수 있다.
var counter = 0;
var foo = () => {
console.log(counter);
if (++counter > 5) clearInterval(timer);
};
var timer = setInterval(foo, 100);
- setInterval은 매개변수로 foo를 받는다. 즉, foo 함수는 setInterval의 콜백 함수이다.
- setInterval함수는 두번째 매개변수로 주어진 시간마다 콜백 함수를 실행한다.
- 위 코드에서는, 100ms(0.1초) 마다 foo함수를 실행시킨다.
- 즉, setInterval함수는 자신의 콜백함수인 foo함수를 원하는 시점에 호출할 수 있는, 호출 시점에 대한 제어권을 갖는다.
2. 매개변수
고차 함수는 콜백 함수에 원하는 인자를 어떤 순서로 넘길 것인지 정할 수 있다.
var arr = [10, 20, 30].map((current, index) => {
console.log(current, index);
return current + 10;
});
console.log(arr);
- map함수는 배열 내 모든 원소에 대해 전달받은 콜백 함수를 실행한다. (참고)
- 따라서 배열 내 모든 원소(10, 20, 30)에 대해 10을 더하는 과정이 실행된다.
- 이 때, 콜백 함수에 어떤 인자를 전달할 것인지는 map함수에 정해져 있다.
- 첫 번째 인자는 배열의 요소 중 현재 값이 전달된다.
- 두 번째 인자로는 현재 값의 인덱스가 전달된다.
- 위의 예시에는 없지만, 세 번째 인자로는 map의 대상이 되는 배열이 전달된다.
3. this
아직 블로그에서는 다루지 않았지만, 함수에서의 this는 전역 객체를 가리킨다. 다만 콜백 함수의 경우, 고차 함수에서 콜백에 별도로 this를 지정할 경우에는 전역이 아닌 그 대상을 참조하게 된다.
고차 함수가 콜백 함수에 별도의 this를 할당하는 과정은 콜백을 받는 실제 메서드를 구현해봄으로써 확인해볼 수 있다.
Array.prototype.map = (callback, thisArg) => {
var mappedArr = [];
for (var i = 0; i < this.length; i++) {
var mapped = callback.call(thisArg || window, this[i], i, this);
mappedArr[i] = mapped;
}
return mappedArr;
};
- Array의 map함수를 직접 구현한 코드이다.
- 첫 번째 인자로 콜백 함수를 받고, 두 번째 인자로 별도로 지정해줄 this를 받는다.
- 현재 배열의 각 요소에 대해 콜백 함수를 실행한다. 이때 call함수를 사용하는데, call함수를 통해 thisArg를 받았을 경우 전달받은 this를, 전달받지 않았다면 전역 객체를 바인딩한다.
위의 예제를 통해, 고차 함수가 콜백에 별도의 this를 지정할 수 있는 까닭은 call/apply 등의 메서드를 통해 명시적으로 this를 바인딩하기 때문임을 알 수 있다.
※ 콜백 함수는 '함수'로 기능한다. 즉, 콜백 함수로 객체의 메서드를 전달하더라도, 그 메서드는 메서드가 아닌 함수로 호출된다.
→ 따라서 일반적인 메서드와 달리, 콜백으로 전달된 객체의 메서드의 this는 객체가 아닌 전역 객체를 바라본다.
콜백 함수 내 this에 다른 값 바인딩하기
- 위에서 언급하였듯, 메서드를 콜백 함수로 전달하면 this가 전역 객체를 바라본다는 문제가 생긴다.
- 이를 해결하기 위한 한 가지 방법으로, this를 다른 변수에 담아서 콜백 함수에서는 해당 변수를 this 대신 사용하게 하는 방법이 있다.
var obj = {
name: 'foo',
func: function () {
var self = this;
return function () {
console.log(self.name);
};
}
};
var callback = obj.func();
setTimeout(callback, 500);
- self 변수에 객체의 this를 담는다. 그리고 obj 객체의 func 함수에서 this가 아닌 self를 사용한다.
- 다만 이 방법은 실제 this를 사용하는 방법이 아니기 때문에 부적절하다.
- ES5부터 추가된 bind 함수를 사용하면 보다 적절하게 this를 바인딩할 수 있다.
var obj1 = {
name: 'foo',
func: function () {
console.log(this.name);
}
};
setTimeout(obj1.func.bind(obj1), 500); // foo 가 출력된다
콜백 지옥 & 비동기 제어
콜백 지옥(Callback Hell)
: 콜백 함수가 중첩되며 코드의 들여쓰기가 깊어지는 현상. (이전에 클린 코드 스터디 글에서 다룬 적이 있다)
- 주로 비동기적인 작업을 수행하기 위해 콜백의 전달이 잦아져 발생한다.
※ 비동기(Asynchronous) 코드: 현재 실행 중인 코드의 완료 여부와 상관 없이 다음 코드로 넘어가는 것. 주로 요청/대기/보류 등의 작업과 관련된다.
콜백 지옥을 해결하는 주된 방법인 Async/Await, Promise 등에 대한 것은 앞서 언급한 클린 코드 스터디에서도 다룬 적이 있으므로, 여기선 따로 다루지 기록하지 않을 예정.
'공부 > JS' 카테고리의 다른 글
[Javascript] 프로토타입 알아보기 - 2 (0) | 2024.02.02 |
---|---|
[Javascript] 프로토타입 알아보기 - 1 (0) | 2024.01.24 |
[Javascript] 클로저 알아보기 - 2 (1) | 2024.01.08 |
[Javascript] 클로저 알아보기 - 1 (1) | 2024.01.05 |
[JavaScript] 실행 컨텍스트에 대하여 (2) | 2023.12.23 |