
콜백 함수에 대해 배움 콜백 함수를 이미 사용 가령 예를 들면, 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);
});
이처럼 다른 코드의 인자로 넘겨주는 함수인 콜백함수의 여러가지 특징과 바람직하지 않은 상황(콜백 지옥)을 만났을 때 어떻게 대처해야 할지 등에 대한 내용을 배움. 이러한 과정에서 자연스럽게 동기 vs 비동기의 개념도 학습.
다른 코드의 인자로 넘겨주는 함수! 인자로 넘겨준다는 얘기는 콜백함수를 넘겨받는 코드가 있다는 얘기. forEach, setTimeout 등
콜백 함수를 넘겨받은 위와 같은 코드 forEach, setTimeout 등은 이 콜백 함수를 필요에 따라 적절한 시점에 실행.(제어권이 그들에게 있는거죠)
(코어자바스크립트의 예시) 다음 날 친구와 8시에 만나기로 한 스펀지밥 😂
(현명하지 않은) 알람을 안 맞춘 스펀지밥..

(현명한) 알람을 맞춘 스밥

action에 대한 제어권은 함수에게 있음 😎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)
그 모습을 아래 표 처럼 정리 🐿
→ 원래 cbFunc()를 수행한다면 그 호출주체와 제어권은 모두 사용자가 됌
→ setInterval로 넘겨주게 되면 그 호출주체와 제어권은 모두 setInterval이 됌
| code | 호출 주체 | 제어권 |
|---|---|---|
| cbFunc(); | 사용자 | 사용자 |
| setInterval(cbFunc, 300); | setInterval | setInterval |
인자
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가 전역객체를 참조한다 예외사항 있음
내부적으로는 어떻게 이걸 가능케 하는 것일까? 별도의 this를 지정하는 방식을 이해하기 위해서. 그리고, 제어권에 대한 이해를 높이기 위해서 map함수를 직접 구현해 보자?
핵심은
call,apply에 있음!
// 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);
});
콜백 함수로 어떤 객체의 메서드를 전달하더라도, 그 메서드는 메서드가 아닌 함수로 호출
var obj = {
vals: [1, 2, 3],
logValues: function(v, i) {
console.log(this, v, i);
}
};
//method로써 호출
obj.logValues(1, 2);
//callback => obj를 this로 하는 메서드를 그대로 전달한게 아님
//단지, obj.logValues가 가리키는 함수만 전달(obj 객체와는 연관이 없음
[4, 5, 6].forEach(obj.logValues);
🤔 콜백 함수 내부에서 this가 문맥에 맞는 객체를 바라보게 할 수는 없까?
🤔 콜백 함수 내부의 this에 다른 값을 바인딩하는 방법
<전통적 방식>
이전에 강제로 this를 제어하는 방법에서 살짝 다뤘던 방식 👀
var obj1 = {
name: 'obj1',
func: function() {
**var self = this; //이 부분!**
return function () {
console.log(self.name);
};
}
};
// 단순히 함수만 전달한 것이기 때문에, obj1 객체와는 상관이 없어요.
// 메서드가 아닌 함수로서 호출한 것과 동일하죠.
var callback = obj1.func();
setTimeout(callback, 1000);
실제로는 this를 사용하는게 아니기도 하고, 번거롭다.
그렇다면, 콜백 함수 내부에서 아에 this를 사용하지 않는다면 어떨까?
var obj1 = {
name: 'obj1',
func: function () {
console.log(obj1.name);
}
};
setTimeout(obj1.func, 1000);
첫 번째 예시보다는 훨씬 간결 하지만 this를 사용하지 않으면서 결과만을 위한 코딩이 되어버렸네요. this를 이용해서 다양한 것을 할 수 있는 장점을 놓침 ㅠㅠ
첫 번째 예시를 재활용하는 방향으로 선회
VSCode 상에서 직접 function을 대입해보면서 순서 따라하기
var obj1 = {
name: 'obj1',
func: function() {
**var self = this; //이 부분!**
return function () {
console.log(self.name);
};
}
};
// ---------------------------------
// obj1의 func를 직접 아래에 대입해보면 조금 더 보기 쉽다!
var obj2 = {
name: 'obj2',
func: obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2, 1500);
// 역시, obj1의 func를 직접 아래에 대입해보면 조금 더 보기 쉽다!
var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
위 방법은 조금 번거롭긴 해도 this를 우회적으로나마 활용하여 원하는 객체를 바라보게 할 수 있음
지금까지 전통적인 방법 하지만 이제는 이러한 부분을 아주 쉽게 해결할 수 있음 바로 bind 메서드를 이용하는 방법.
<가장 좋은 방법 → bind메서드의 활용>
var obj1 = {
name: 'obj1',
func: function () {
console.log(this.name);
}
};
//함수 자체를 obj1에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj1로 고정해줘!
setTimeout(obj1.func.bind(obj1), 1000);
var obj2 = { name: 'obj2' };
//함수 자체를 obj2에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj2로 고정해줘!
setTimeout(obj1.func.bind(obj2), 1500);