우리는 이미 콜백 함수를 사용해 왔다, 예를 들면 setTimeout
, 배열에 대한 forEach
등...
// setTimeout
setTimeout(function() {
console.log("Hello, world!");
}, 1000);
// forEach
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(number) {
console.log(number);
});
다른 코드의 인자로 넘겨주는 함수이다, 인자로 넘겨준다는 얘기는 콜백함수를 넘겨받는 코드가 있다는 얘기다. forEach
, setTimeout
등이 있다
콜백 함수를 넘겨받은 위와 같은 코드 forEach
, setTimeout
등은 이 콜백 함수를 필요에 따라 적절한 시점에 실행하게 된다(제어권이 그들에게 있다)
이하 그림 설명..
callback = call(부르다) + back(되돌아오다) = 되돌아와서 호출해줘!
다시 말하자면, 제어권을 넘겨주는 대신 너가 알고 있는 그 로직으로 처리해줘!
즉 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수. 콜백 함수를 위임받은 코드는 자체적으로 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행 <= 이 적절한 시점 역시 제어권이 있는 위임받은 코드가 가지고 있다.
콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.
아래 예시에서는 콜백 함수의 제어권을 받은 코드(=setinterval)가 언제 콜백함수를 호출할지에 대한 제어권을 가지게 된다, 0.3초라는 적절한 시점을 본인의 함수에 적어놓는대로 실행하는 것이다.
var count = 0;
// timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지'
// 알려주는 id값
var timer = setInterval(function() {
console.log(count);
if(++count > 4) clearInterval(timer);
}, 300);
var count = 0;
var cbFunc = function () {
console.log(count);
if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)
그 모습을 아래 표 처럼 정리해본다.
Map
함수는 각 배열 요소를 변환하여 새로운 배열을 반환한다. 기존 배열을 변경하지 않고, 새로운 배열을 생성한다.
// 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 ]
currentValue, index
이 변수의 순서를 바꾸면 어떻게 될까?
// 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
의 의미를 사람처럼 이해할 수 없다. 따라서 의도치 않은 값이 나온다.
이처럼 map메서드를 호출해서 원하는 배열을 얻고자 한다면 정의된 규칙대로 작성해야 한다.
이 모든것은 전적으로 map메서드, 즉 콜백 함수를 넘겨받은 코드에게 그 제어권이 있다. 인자까지도 제어권이 그것에게 있다.
제어권이 넘어갈 map함수의 규칙에 맞게 '나는' 호출해야 한다.
콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조한다.
제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.
핵심은
call
,appply
에 있다.
// Array.prototype.map을 직접 구현해봤어요!
Array.prototype.mapaaa = function (callback, thisArg) {
var mappedArr = [];
for (var i = 0; i < this.length; i++) {
// call의 첫 번째 인자는 thisArg가 존재하는 경우는 그 객체, 없으면 전역객체
// call의 두 번째 인자는 this가 배열일 것(호출의 주체가 배열)이므로,
// i번째 요소를 넣어서 인자로 전달
var mappedValue = callback.call(thisArg || global, this[i]);
mappedArr[i] = mappedValue;
}
return mappedArr;
};
const a = [1, 2, 3].mapaaa((item) => {
return item * 2;
});
console.log(a);
제어권을 넘겨받을 코드에서 call/ apply 메서드의 첫 번째 인자에서 콜백함수 내부에서 사용될 this를 명시적으로 binding 하기 때문에 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);
});