자바스크립트 - callback

kdeun1·2021년 8월 4일
0
post-thumbnail


이전에 쓴 글의 내용 중에 함수는 function 키워드를 써야하며, 함수 전체 텍스트가 저장되는 것을 알 수 있다. 함수는 소괄호의 쌍 (함수명())을 만나게 되면 함수가 호출되어 실행되고, 함수 실행 컨텍스트가 생성된다.

함수의 종류로서 콜백 함수(callback function)는 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수이다. 콜백 함수는 제어권과 관련이 깊은데, 다른 코드에게 함수 전체 문장을 넘겨주면서 제어권도 같이 함께 위임한 함수이다.

callback 함수의 특징

함수 aaa의 인자로 콜백함수 bbb를 전달하면, aaa가 bbb의 제어권을 갖게 된다.
this가 바뀌지 않는 한 aaa에 미리 정해놓은 방식에 따라 bbb 함수를 호출한다.
콜백함수의 제어권을 넘겨받은 함수는 호출 시점과 인자의 값 그리고 this의 바인딩같은 제어권을 가진다.

호출한 함수가 가지는 콜백함수의 제어권

콜백함수의 실행시점에 대한 제어권

var count = 0;
var timer;
var callbackFn = function() {
  console.log(++count);
  if (count === 5) {
    clearInterval(timer);
  }
}

timer = setInterval(callbackFn, 1000);

setInterval의 구문
var intervalID = setInterval(func, [delay, arg1, arg2, ...]);
var intervalID = setInterval(function[, delay]);
var intervalID = setInterval(code, [delay]);

timer 변수에는 setInterval을 유니크하게 식별할 수 있는 interval ID가 반환되어 할당된다. setInterval 함수가 콜백함수를 1000ms마다 실행하는 제어권을 갖는다.

콜백함수의 인자에 대한 제어권

const accArr = [];
[10, 20, 30, 40, 50].forEach((cur, idx) => {
  accArr.push([cur, idx]);
});

console.log(accArr);

forEach의 구문
arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])

forEach 메소드는 첫 번째 인자로 callback함수를 받고 두 번째 인자로 생략 가능한 thisArg를 받는다. 이 때, thisArg는 callback을 실행할 때 this로 사용할 값이다. thisArg를 생략하게 되면 콜백함수도 일반적인 함수와 마찬가지로 전역 객체에 바인딩된다. MDN 문서에 있는 구문대로 콜백함수의 제어권을 넘겨받은 코드는 콜백함수를 호출 할 때, 인자에 어떤 값들을 순서대로 넘길 것인지에 대한 제어권을 가진다.

콜백함수의 this에 대한 제어권

콜백함수도 함수이다. 그러므로 기본적으로 this는 globalThis를 가리킨다. 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.
이전 글(this)에서 마지막 단원에 call, apply, bind를 사용하여 명시적으로 this를 변경하는 방법을 배웠다. 요소를 순회하면서 콜백함수를 반복 호출하는 일부 메소드(forEach, map, filter, some, every, find, findIndex, flatMap, from)들은 별도의 인자로 this를 받기도 한다.

var clickEventHandler = function(e) {
  console.log('this : ' + this + ', e : ' + e);
};

document.querySelector('#testId').addEventListner('click', clickEventHandler);

addEventListener의 구문
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);

익명함수를 사용하여 첫 번째 전달인자인 clickEventHandler 핸들러 함수에 매개변수를 전달한다. addEventListener 함수의 콜백함수의 this는 addEventListener를 호출한 주체인 HTML 요소를 가리킨다. 예제에서의 this는 [object HTMLDivElement]인 #testId를 가리킨다. 만일 화살표 함수를 사용한다면 this는 globalThis를 가리킨다.

콜백 함수는 함수이다.
콜백 함수로 어떤 객체의 메소드를 전달하더라도 그 메소드는 메소드가 아닌 함수로서 호출된다. 메소드의 this는 호출 주체를 가리키지만, 함수의 this는 일반적으로 globalThis를 가리킨다. 콜백 함수도 함수의 this 규칙과 동일한 규칙을 가진다.

var aaa = {
  arr: [1, 2, 3],
  get: function(val, idx) {
    console.log(`this : ${this}, val : ${val}, idx : ${idx}`);
  }
}

aaa.get(10, 11); // this : [object Object], val : 10, idx : 11

[4, 5, 6].forEach(aaa.get);
// this : [object Window], val : 4, idx : 0
// this : [object Window], val : 5, idx : 1
// this : [object Window], val : 6, idx : 2

aaa.get(10, 11); 로 호출한 경우에는 this가 aaa이다. 하지만 forEach의 콜백함수로 aaa.get을 넘겨주면 이는 호출하는 것이 아니라 함수 전체 문장을 파라미터로 콜백함수로 넘겨주게 되는 것이다. 별도의 this를 지정하지 않아 함수 내부에서의 this는 globalThis인 window를 가리킨다.

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

옛날 프론트엔드에서 보이는 var self = this; 코드는 직관적으로 변수에 this를 담는 방법이다. 회사 솔루션의 잔재로 남아있는 레거시 코드인 self는 과거 그 당시에 개발할 때는 쉽고 단순하게 개발하였겠지만, 실제 이 코드를 보면서 분석하고 추가 개발 및 리펙토링을 하였을 때는 ES5의 var 키워드, 콜백 지옥, 전역 변수에 남발되는 환장의 콜라보레이션 self를 보면서 많은 괴로움을 토해냈다.

var aaa = {
  b: 'test1',
  get: function() {
    console.log(this.b);
  }
}

setTimeout(aaa.get.bind(aaa), 1000); // 'test1'

var bbb = {
  b: 'test2',
}

setTimeout(aaa.get.bind(bbb), 1000); /// 'test2'

개발자는 콜백 함수에 .bind() 메소드를 사용해서 this를 다른 값으로 바인딩하여 위의 코드와 같이 self를 쓰지 않도록 변경할 수 있다.


참고

profile
프론트엔드 개발자입니다.

0개의 댓글