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

이예슬·2023년 1월 8일
0

콜백함수는 다른 코드의 인자로 넘겨주는 함수를 말한다. 콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행한다.

콜백함수는 다른 코드에게 인자로 넘겨줌으로써 그 제어권도 함꼐 위임한 함수이다. 콜백 함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.

제어권

호출시점

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

setInterval의 구조는 다음과 같다

var intervalId = scope.setInterval(func, delay[, param1, param2, ...]);

우선 scope에는 윈도우 객체 또는 Worker의 인스턴스가 들어올 수 있다. 매개변수로는 func, delay 값을 반드시 전달해야 하고 세번쨰 매개변수는 선택적이다. setInterval을 실행하면 반족적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID 값이 반환된다. 이를 변수에 담는 이유는 반복 실행되는 중간에 종료할 수 있게 하기 위함이다.

setInterval은 콜백함수의 제어권을 넘겨받아 적절한 시점에 이 함수를 실행한다. 이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

인자

map 메서드는 첫번째 인자로 콜백 함수를 받고 생략 가능한 두번쨰 인자로 콜백 함수 내부에서 this로 인식할 대상을 특정할 수 있다. 콜백함수의 첫 번쨰 인자에는 배열 요소 중 현재값이 두번째 인자에는 현재값의 인덱스가 세번째 인자에는 map 메서드의 대상이 되는 배열 자체가 담긴다.

map 메서드에 정의된 규칙에는 콜백 함수의 인자로 넘어올 값들 및 그 순서도 포함돼 있다. 콜백 함수를 호출하는 주체가 사용자가 아닌 map 메서드이므로 map 메서드가 콜백 함수를 호출하는 주체가 사용자가 아닌 map 메서드이므로 map 메서드가 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길것인지가 전적으로 map 메서드에게 달렸다. 이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.

this

콜백함수는 함수이므로 기본적으로 this가 전역객체를 참조하지만 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.

별도의 this를 지정하기 위해서는 call/apply 메서드를 활용할 수 있다.

아래 코드는 this를 지정하는 방식에 대한 이해를 돕기 위해 map 함수를 구현한 코드이다.

제어권을 넘겨받을 코드에서 call/apply 메서드의 첫 번째 인자에 콜백 함수에서의 this가 될 대상을 명시적으로 바인딩하고 있다.

Array.prototype.map = function(callback, thisArg){
  var mappedArr = []
  for(var i = 0; i < this.length; i++){
    var mappedValue = callback.call(thisArg || window, this[i], i, this)
    mappedArr[i] = mappedValue
  }
  return mappedArr
}

콜백함수는 함수다.

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

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

위의 내용을 통해서 알 수 있듯이 객체의 메서드를 콜백함수로 전달하면 해당 객체를 this로 바라볼 수 없다. 하지만 경우에 따라 콜백함수 내부에서 this가 객체를 바라보게 하고 싶은 경우가 있을 수 있다. 그럴 때는 아래와 같은 방식을 활용할 수 있다.

  1. this를 다른 변수에 담아 콜백 함수로 활용할 함수에는 this 대신 그 변수를 사용하게 하고 이를 클로저로 만드는 방식

    var obj1 = {
      name: 'obj1', 
      func: function(){
        var self = this;
        return function(){
          console.log(self.name)
        }
      }
    }

    obj1.func 메서드 내부에서 self 변수에 this를 담고 익명함수를 선언함과 동시에 반환했다.

    코드는 조금 복잡해졌지만 해당 코드와 같이 this를 지정해줄 경우 아래와 같이 함수를 재활용할 수 있다.

    
    var callback = obj1.func()
    setTimeout(callback, 1000)
    
    var obj2 = {
      name: 'obj2',
      func: obj1.func
    }
    
    var callback2 = obj2.func()
    setTimeout(callback2, 1500)
    
    var obj3 = {name : 'obj3'}
    var callback3 = obj1.func.call(obj3)
    setTimeout(callback3, 2000)
  1. bind 메서드를 활용해서 내부 this 값을 바인딩하는 방법

    하지만 ES5 이후 bind 메서드가 등장하면서 간단하게 this를 바인딩해줄 수 있게됐다.

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

콜백 지옥과 비동기 제어

동기적인 코드는 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식이다. 반대로 비동기적인 코드는 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다. CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드이다. 반면 웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에 대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기하는 등 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적인 코드이다.

비동기 제어를 위해 콜백함수를 사용하다 보면 콜백 지옥에 빠지기 쉽다. 최근 ECMAScript에는 Promise, Generator, async/await 등 콜백 지옥에서 벗어날 수 있는 방법들이 새로이 등장하고 있다.


<코어 자바스크립트> 정재남, 위키북스(2019)

profile
꾸준히 열심히!

0개의 댓글