[TIL] Callback Function (1)

·2023년 10월 18일
0

TIL

목록 보기
8/85
post-thumbnail

콜백함수란?

  • 다른 코드의 인자(매개변수)로 넘겨주는 함수!
  • 콜백 함수를 넘겨받은 forEach, setTimeout 등은 이 콜백 함수를 필요에 따라 적절한 시점에 실행하게 된다. -> 제어권이 이들에게 있다는 것!
  • 즉, 콜백 함수는 다른 함수(or메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수
  • 콜백 함수를 위임받은 함수(or메서드)는 자체적으로 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.
// setTimeout
setTimeout(function() {
  console.log("Hello, world!");
}, 1000);

// forEach
const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function(number) {
  console.log(number);
});

제어권

1. 호출 시점

💡 콜백 함수의 제어권을 넘겨받은 함수는 콜백 함수 호출 시점에 대한 제어권을 갖는다.

var count = 0;
var cbFunc = function () {
	console.log(count);
	if (++count > 4) clearInterval(timer);
};
// timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지' 알려주는 id값
var timer = setInterval(cbFunc, 300);

// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)
  • 콜백 함수의 제어권을 넘겨받은 함수(=setInterval)가 언제 콜백함수를 호출할지(호출 시점)에 대한 제어권을 가지게 된다!

  • cbFunc(); // 직접 콜백함수를 실행하면 한번만 실행하고 만다.

    code호출 주체제어권
    cbFunc();사용자사용자
    setInterval(cbFunc, 300);setIntervalsetInterval

2. 인자

💡 콜백 함수의 제어권을 넘겨받은 함수는 콜백 함수에 대한 인자의 제어권을 갖는다.

// map 함수에 의해 새로운 배열을 생성해서 newArr에 담고 있네요!
var newArr = [10, 20, 30].map(function (currentValue, index) {
	console.log(currentValue, index);
	return currentValue + 5;
});
console.log(newArr);

// -- 실행 결과 --
// 10 0
// 20 1
// 30 2
// [ 15, 25, 35 ]
  • map()의 콜백 함수에서 currentValueindex의 순서를 바꾸면 어떻게 될까?
// map 함수에 의해 새로운 배열을 생성해서 newArr에 담고 있네요!
var newArr2 = [10, 20, 30].map(function (index, currentValue) {
	console.log(index, currentValue);
	return currentValue + 5;
});
console.log(newArr2);

// -- 실행 결과 --
// 10 0
// 20 1
// 30 2
// [ 5, 6, 7 ] -> 의도하지 않은 결과
  • index, currentValue는 사람이 이해할 수 있는 변수명일 뿐, 컴퓨터는 첫번째 매개변수를 currentValue로, 두번째 매개변수를 index로 인식한다.
  • map 메서드를 호출해서 원하는 배열을 얻고자 한다면 정의된 규칙대로 작성해야 한다. (콜백 내부의 매개변수 포함)
  • 콜백 함수의 인자(의 순서)의 제어권을 map()이 갖는다!
  • 즉, 콜백 함수를 넘겨받은 함수(map)는 콜백 함수의 인자(의 순서)까지도 제어권을 갖는다.

3. this

💡 제어권을 넘겨받을 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다. (ex. addEventListener)

  • 콜백 함수도 함수이기 때문에 기본적으로는 this가 전역 객체를 참조한다.
  • 그렇지만 제어권을 넘겨받은 함수는 별도의 this를 지정할 수 있다.
// setTimeout은 내부에서 콜백 함수를 호출할 때, call 메서드의 첫번째 인자에 전역객체를 넘겨요
// 따라서 콜백 함수 내부에서의 this가 전역객체를 가리켜요
setTimeout(function() { console.log(this); }, 300); // Window { ... }

// forEach도 마찬가지로, 콜백 뒷 부분에 this를 명시해주지 않으면 전역객체를 넘겨요!
// 만약 명시한다면 해당 객체를 넘기긴 해요!
[1, 2, 3, 4, 5].forEach(function (x) {
	console.log(this); // Window { ... }
});

//addEventListener는 내부에서 콜백 함수를 호출할 때, call 메서드의 첫 번째
//인자에 addEventListener메서드의 this를 그대로 넘겨주도록 정의돼 있어요(상속)
document.body.innerHTML += '<button id="a">클릭</button';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});
  • 내부적으로 이걸 어떻게 가능하게 하는지 이해하기 위해 map() 메서드를 직접 구현해보자. (별도의 this를 지정하는 방식을 이해해보자.)
  • 핵심은 call,apply를 통한 명시적 binding에 있다.
// Array.prototype.map을 직접 구현해보자!
Array.prototype.map123 = function (callback, thisArg) {
  // map123 함수에서 return할 결과 배열
  var mappedArr = [];

  for (var i = 0; i < this.length; i++) {
    // 여기서 this 는 [1, 2, 3]
    var mappedValue = callback.call(thisArg || globalThis, this[i]);
    // call의 첫 번째 인자는 thisArg가 존재하는 경우는 그 객체, 없으면 전역객체
    // call의 두 번째 인자는 this가 배열일 것(호출의 주체가 배열)이므로, i번째 요소를 넣어서 인자로 전달
    mappedArr[i] = mappedValue;
  }
  console.log(thisArg); // { name: 'hyewon' }
  return mappedArr;
};

var newArr = [1, 2, 3].map123(
  function (number) {
    return number * 2;
  },
  { name: "hyewon" } // 별도의 this가 될 대상을 지정해보자.
);
console.log(newArr); // [ 2, 4, 6 ]
  • 즉, 제어권을 넘겨받을 함수(map)에서 call / apply 메서드의 첫번째 인자에서 콜백 함수 내부에서 사용될 this를 명시적으로 binding 하기 때문에 this에 다른 값이 담길 수 있다.
  • 콜백 함수 내부에서 사용될 this의 제어권 map()이 갖는다!

콜백 함수는 함수다

  • 콜백 함수로 어떤 객체의 메서드를 전달하다라도, 그 메서드는 메서드가 아닌 함수로 호출한다.
var obj = {
	vals: [1, 2, 3],
	logValues: function (v, i) {
    if (this == global) {
      console.log("this -> global", v, i);
    } else {
      console.log(this, v, i);   
    }
  },
};

//method로써 호출
obj.logValues(1, 2); // (1) obj 1 2

//callback => obj를 this로 하는 메서드를 그대로 전달한게 아니에요
//단지, obj.logValues가 가리키는 함수만 전달한거에요(obj 객체와는 연관이 없습니다)
[4, 5, 6].forEach(obj.logValues); // (2) global 4 0 (3) global 5 1 (4) global 6 2

[실행결과]
{ vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
this -> global 4 0
this -> global 5 1
this -> global 6 2

  • cf) forEach callback함수의 첫번째 매개변수는 배열 각 요소값, 두번째 매개변수는 인덱스로 잡혀서 들어간다. (세번째 매개변수는 forEach를 호출한 배열)
profile
느리더라도 조금씩, 꾸준히

0개의 댓글