코딩쌀롱

[코어 자바스크립트] 4. 콜백 함수 본문

[코어 자바스크립트] 4. 콜백 함수

이브✱ 2020. 12. 23. 03:15

4-1 콜백 함수란?

 'callback'은 되돌아 호출해달라는 명령이다. 어떤 함수 X를 호출하면서 '특정 조건일 때 함수 Y를 실행해서 나에게 알려달라'는 요청을 함께 보낸 것이다. 함수 X는 해당 조건이 갖춰졌는지 여부를 스스로 판단하고 Y를 직접 호출한다. 

 이처럼 콜백 함수는 함수 또는 메서드에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수다.

 

4-2 제어권

호출 시점

let count = 0;
const timer = setInterval(function () {
   console.log(count);
   if (++count > 4) clearInterval(timer);
}, 300);

 이 코드를 실행하면 콘솔창에는 0.3초에 한 번씩 숫자가 0부터 1씩 증가하며 출력되다가 4가 출력된 이후 종료된다.

 제어권을 넘겨받은 setInterval이 스스로의 판단에 따라 적절한 시점에(0.3초마다) 이 익명 함수를 실행했다. 이처럼 콜백함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

 

인자

let newArr = [10, 20, 30].map(function (currV, idx) {
   console.log(currV, idx);
   return currV + 5;
});
console.log(newArr);

// 10 0
// 20 1
// 30 2
// [15, 25, 35]

 map 메서드에 정의된 규칙에는 콜백 함수의 인자로 넘어올 값들, 순서도 포함돼 있다. (idx,  currV 이렇게 순서를 바꾼다고 해도 첫번째 인자가 현재 요소를 의미한다는 것이 변하지 않는다.)

 콜백함수를 호출하는 주체가 사용자가 아닌 map 메서드이므로 map 메서드가 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 거쇼인지가 전적으로 map 메서드에게 달린 것이다. 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 인자에 어떤 값들을 순서로 넘길 것인지에 대한 제어권을 가진다.

 

this

setTimeout(function () { console.log(this); }, 300);        // (1) Window{...}

[1, 2, 3, 4, 5].forEach(function (x) {
   console.log(this);                                       // (2) Window{...}
});

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
   .addEventListener('click', function (e) {
      console.log(this, e);                          // (3) <button id="a">클릭</button>
   }                                                 // MouseEvent { isTrusted: true, ...}
);

(1) setTimeout은 내부에서 콜백 함수를 호출할 때 call 메서드의 첫 번째 인자에 전역객체를 넘기기 때문에 this는 전역객체를 가리킨다.

(2) forEach는 별도의 인자로 this를 넘겨주지 않았기 때문에 전역객체를 가리킨다.

(3) addEventListener 메서드의 this를 그대로 넘기도록 정의돼 있기 때문에 콜백 함수 내부에서의 this가 addEventListener를 호출한 주체인 HTML 엘리먼트를 가리킨다.

 

4-3 콜백 함수는 함수다

 콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로서 호출된다.

const 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

(1) 메서드의 이름 앞에 점이 있으니 메서드로서 호출한 것. this는 obj를 가리킴

(2) forEach 함수의 콜백 함수로서 전달. obj를 this로 하는 메서드를 그대로 전달한 것이 아니다. obj.logValues가 가리키는 함수만 전달한 것이다. 이 함수는 메서드로서 호출할 때가 아닌 한 obj와의 직접적인 연관이 없어진다. forEach에 의해 콜백이 함수로서 호출되고, this를 지정하는 인자를 지정하지 않았으므로 함수 내부의 this는 전역 객체를 바라보게 된다.

 

4-4 콜백 함수 내부의 this에 다른 값 바인딩하기

ES5의 bind메서드 이용하기

const obj1 = {
   name: 'obj1',
   func: function () {
      console.log(this.name);
   }
};
setTimeout(obj1.func.bind(obj1), 1000);

const obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500);

 

4-5 콜백 지옥과 비동기 제어

콜백 지옥 : 콜백함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 매우 깊어지는 현상

동기적 코드 : 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식

비동기적 코드 : 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감

  - setTimeout : 특정 시간이 경과되기 전까지 어떤 함수의 실행을 보류

  - addEventListener : 사용자의 개입이 있을 때 비로소 어떤 함수를 실행하도록 대기

  - XMLHttpRequest : 요청하고 응답이 왔을 때 어떤 함수를 실행하도록 대기

 

해결방법1. 익명의 콜백함수를 모두 함수 표현식으로 전환하는 것

 코드의 가독성을 높이고, 함수 선언과 함수 호출만 구분할 수 있다면 위에서부터 아래로 순서대로 읽기 쉽다.

 일회성 함수를 전부 변수에 할당하는 것이 마뜩잖을 수 있다. 그래서 나온 것들이 방법2이다.

 

해결방법2. Promise, Generator, async/await

 

 

 


정재남 작가님의 〔 코어 자바스크립트 〕 책을 공부하며 정리했습니다.

Comments