다른 코드에게 인자로 넘겨줌으로써 그 제어권도 함께 위임하는 함수이다.
콜백 함수에 대한 제어권을 넘겨받은 코드가 가지는 제어권에는 3가지가 있다.
var count = 0;
var timer = setInterval(function() {
console.log(count);
if (++count > 4) clearInterval(timer);
}, 300)
위 코드를 실행하게 되면 0.3초 간격으로 0~4까지 출력되고 실행이 종료된다. setInterval이 콜백 함수를 인자로 받아 내부 로직에 의해 콜백 함수를 실행한 것이다.
이처럼 제어권을 넘겨받은 코드는 콜백 함수의 호출 시점에 대한 제어권을 가진다.
var newArr = [10,20,30].map(function(value, index) {
console.log(value, index);
return value + 5;
});
console.log(newArr);
// 10 0
// 20 1
// 30 2
// [15, 25, 35]
map은 배열의 모든 요소들을 하나씩 꺼내어 콜백 함수를 반복 호출하고, 그 결과물들을 모은 새로운 배열을 반환하는 메소드이다. 콜백 함수의 첫번째 인자에는 배열의 요소 중 현재 값을, 두번째 인자에는 현재 값의 인덱스를, 세번째 인자에는 map의 대상이 되는 배열 자체가 담긴다. 이 순서는 map 메소드에 정의된 규칙이므로 변경할 수 없다. 다른 순서로 인자를 넘기게 되면 원하는 값을 얻을 수 없을 것이다.
이처럼 제어권을 넘겨받은 코드는 콜백 함수의 인자에 어떤 값들을 어떤 순서로 넘길건지 인자에 대한 제어권을 가진다.
setTimeout(function() { console.log(this); }, 300);
// Window { ... }
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this);
});
// Window { ... }
document.body.innerHTML += "<button id="a">클릭</button>";
document.body.querySelector('#a')
.addEventListener('click', function(e) {
console.log(this);
});
// <button id="a">클릭</button>
setTimeout은 내부에서 this에 전역객체를 넘겨주게 되어있다.
forEach는 this를 지정해서 넘겨줄 수 있지만, 위 예시에서는 따로 지정하지 않았기 때문에 전역객체를 가리키게 된다.
addEventListener는 내부에 콜백 함수가 자신의 this를 상속받도록 정의되어 있기 때문에 호출한 주체를 가리키게 된다.
콜백 함수도 함수이므로 기본적으로 this는 전역객체를 가리키지만, 제어권을 넘겨받은 코드에서 별도로 this를 지정해주면 그 대상을 참조하게 된다.
이렇게 제어권을 넘겨받은 코드는 콜백 함수의 this에 대한 제어권을 가진다.
어떤 객체의 메소드를 콜백 함수로 전달하면 메소드가 아닌 함수로 동작한다.
var obj = {
vals: [1, 2, 3],
logValues: function(v, i) {
console.log(this, v, i);
}
};
obj.logValues(1, 2); // (1) { vals: [1, 2, 3], logValues: f } 1 2
[4, 5, 6].forEach(obj.logValues); // (2)
// Window { ... } 4 0
// Window { ... } 5 1
// Window { ... } 6 2
(1)에서는 logValues를 메소드로 호출했기 때문에 obj를 가리킨다.
(2)에서는 forEach의 콜백 함수로 logValues를 넘겨주었기 때문에 메소드가 아닌 함수로 동작하여 전역객체를 가리키게 된다.
어떤 함수의 인자로 객체의 메소드를 넘겨주더라도 결국 메소드가 아닌 함수일 뿐이다.
콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상이다. 주로 비동기 작업을 수행하기 위해 이런 형태가 등장하곤 하는데 가독성도 떨어지고 코드를 수정하기도 어렵다.
setTimeout(function() {
...
setTimeout(function() {
...
setTimeout(function() {
...
setTimeout(function() {
...
}, 500);
}, 500);
}, 500);
}, 500);
별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적인 코드이다. 현대의 자바스크립트는 웹의 복잡도가 높아진 만큼 비동기적 코드의 비중이 매우 높아졌고, 그래서 콜백 지옥에 빠지기도 쉬워졌다.
비동기 제어를 위한 장치로 ES6에는 Promise, Generator 등이 도입되었고, ES2017부터는 async/await가 도입되었다.