: 다른 코드의 인자로 넘겨주는 함수.
setTimeout(function(){
console.log('time');
}, 1000);
arr.forEach(function (num) {
console.log(num);
}
action에 대한 제어권을 함수에게 위임하는 개념이다. 내가 직접 함수를 호출하는게 아닌, 콜백 함수 자체적으로 내부 로직에 맞춰 제어권을 가지고 기능을 실행하게 하는 것.
호출 시점에 대한 제어권을 갖는다.
var func = function () {
console.log(count);
if(++count > 4) clearInterval(timer);
}
//우리가 직접 호출
func();
//setInterval로 넘겨줌
var timer = setInterval(func, 3000);
//3초라는 실행 타이밍을 주체적으로 가짐
인자에 대한 제어권
map은 기존 배열을 변경하지 않고 새로운 배열을 생성한다.
var newArr = [10, 11, 12]
newArr.map(function(item, index){ //콜백함수
console.log(item, index); //10 0 11 1 12 2
});
console.log(newArr); //undefined
현재 코드는 return이 없으면 반환값이 없어서 새로운 배열이 없기때문에 newArr를 정의하지 못한다.
여기서, item과 index는 매개변수로써 배열의 값을 나타내준다.
**❓ 그렇다면 이 콜백 함수의 변수의 이름을 바꾼다면? 값도 바뀔까?
❗ 결론은 바뀌지 않는다**. 이게 콜백 함수가 인자에 대한 규칙에 맞게 제어권을 가진다는 것을 의미한다. 사람이 알아듣는 언어를 사용하여도 첫 번째 자리는 배열의 값, 두 번째 값은 인덱스 값을 가지는 거다.
this
앞에서 this를 공부했을 때, 함수는 전역 객체를 메서드는 앞선 객체를 호출하는 것을 배웠고 호출 함수 또한 함수이기 때문에 전역 객체를 참조한다고 공부했다.
💡 **하지만 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.**제어권을 넘겨받을 코드에서 call/apply 메서드의 첫 번째 인자에서 콜백 함수 내부에서 사용될 this를 명시적으로 binding 하기 때문에 this에 다른 값이 담길 수 있다.
// Array.prototype.map을 직접 구현해봤어요!
Array.prototype.mapaaa = function (callback, thisArg) { //callback이라는 함수, this
var mappedArr = [];
for (var i = 0; i < this.length; i++) { //this => 호출 주체가 배열임. 배열을 가리
// 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);
이것이 직접 수제작한 map 함수이다.
배열.map((함수));
의 형태로 사용이 되지만 위에서 callback.call(thisArg || global, this[i]);를 볼 때, call이 전역 변수만 받게 하는게 아니라 this값이 있다면 thisArg에 this값도 받게 하기 때문에
>>콜백함수의 예외상황<<
배열.map((함수), this값);
이런식으로 사용할 경우 this값에 전역변수가 아닌 다른 값이 담길 수 있게 된다.
다음의 코드를 보자
var obj = {
logValues : function(v,i){
console.log('hi');
},
val : 1
}
[1, 2, 3].forEach(obj.logValues);
마지막 줄만 보면, forEach에는 콜백 함수가 들어가야 하는데 obj라는 오브젝트의 객체가 들어간 것처럼 보인다. 하지만 obj.logValues는 함수의 형태를 가지기 때문에 콜백 함수로써의 기능을 수행한다.
따라서 들어간 값이 obj라는 호출객체가 있는 것 처럼 보이지만 실제로는 함수의 형태만 들어간 것이기 때문에 forEach에서 obj를 this 범위로 사용할 수 없다. logValues 안에 함수는 전역객체를 this값으로 사용하게 된다.
현재 전통적인 방식으로는 함수의 상위 객체 값을 저장하는 방식으로 다른 값을 바인딩할 수 있다.
var obj1 = {
name: 'obj1',
func: function() {
**var self = this; //이 부분! this를 self에 할당**
return function () {
console.log(self.name);
};
}
};
이런 함수를 실행할 때 함수가 끝나게 되어도 함수에 저장한 값이 살아있는 것을 클로저라고 할 수 있다.
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
setTimeout(obj1.func, 1000);
이렇게 할 수 있기도 하지만, this를 사용하지 않으면서 this에 대한 장점을 놓치게 된다.
var obj2 = {
name: 'obj2',
func: obj1.func //this를 바인딩한 obj1의 func를 불러옴
};
var callback2 = obj2.func();
setTimeout(callback2, 1500); //obj2가 뜨게
이렇게하면, 번거롭지만 this를 우회적으로나마 원하는 객체를 바라보게 할 수 있다.
func()의 상위객체가 obj2이기 때문에..
var obj3 = { name: 'obj3'};
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
이렇게도 사용이 가능하다. 이 방식은 객체에 값이 담겨져있고
call을 사용해서 즉시 실행으로 obj3의 name값을 불러오게 함으로 obj1의 func을 사용한다.
여기서 bind 메소드를 이용해서 this값을 불러오는 방식도 있다.
var obj1 = {
name: 'obj1',
func: function() {
console.log(obj1.name);
},
};
var obj2 = { name: 'obj1' };
setTimeout(obj1.func.bind(obj1), 1000);
setTimeout(obj1.func.bund(obj2), 1000); //
이런식으로 활용하면.. obj1에 있는 func에는 obj1.name이 바인딩 되어있으니까 bind로 obj1의 name을 받아오게 된다.
마찬가지로 obj2라는 값에서 name을 받아오는 형식을 이용해 obj2의 영역을 bind해서 name을 받아오는 식으로 bind를 이용할 수 있다.
콜백 지옥이란?
매개변수 자리에 익명 함수(콜백 함수)가 계속 들어가면 엄청나게 들여쓰기가 많이 들어가면서 콜백 지옥이 된다.
주로 이벤트 처리 및 서버 통신과 같은 비동기 적 작업을 수행할 때 발생한다.
이런 콜백 지옥은 유지보수와 가독성이 지옥과도 같다.
**동기 vs 비동기
- 비동기 : asynchronous
실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
별도의 요청, 실행, 대기, 보류 등과 관련된 코드는 모두 비동기적 코드
setTimeout(function () {
console.log("주문1");
}, 1000);
console.log("주문2");
코드 순서 상이라면 “주문1”이 먼저 실행 되어야 한다. 하지만 setTimeout은 비 동기적 작업이기 때문에 1초 나중에 실행되도록 되어있다. 때문에 주문 1이 들어왔지만 끝나고 난 다음 다음 코드를 읽는 게 아니라 1초란 시간이 걸리기 때문에 주문 2가 읽히게 되고 주문2 다음 주문1이 실행되게 된다.
통신이 들어간(서버와의 작업) 코드는 대부분 비동기적이다.
비동기 작업
: 순서를 보장하지 않는다. 그래서 순서를 보장하는 것처럼 보이게 하는 작업을 비동기 작업이라고 한다.
promise (ES6)
비동기 처리에 대해 처리가 끝나면 알려달라는 약속(promise)이다.
비동기 작업이 완료될 때 resolve(성공), reject(실패)를 호출한다.
new Promise(function(resolve){
setTimeour(function(){
var name = 'coffee';
console.log(name);
resolve(name);
}, 500);
}).then(function(prev){ //promise를 작성(chaining)
return new Promise(function (resolve){
setTimeout(function () {
var name = prev + "latte";
console.log(name);
}
}, 500);
}).then ... //promise를 작성(chaining)
이렇게 반복되는 로직을 가독성과 유지보수 좋게 바꾸는 작업이 필요하다. (refactoring)
똑같은 함수가 반복되니까 변수로 넣어서 내부의 로직을 생성하게 하는 방법.
var addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('아메리카노')()
.then(addCoffee('에스프레소')
.then(addCoffee('어쩌구')
.
.
.;
이렇게 addCoffee에 함수를 넣어서 then다음에 계속 넣게 된다면 전보다 가독성이 있어진다.
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
var coffeeGenerator = function* () { //<<generator함
var espresso = yield addCoffee('', '에스프레소');
console.log(espresso);
var americano = yield addCoffee(espresso, '아메리카노');
console.log(americano);
var mocha = yield addCoffee(americano, '카페모카');
console.log(mocha);
var latte = yield addCoffee(mocha, '카페라떼');
console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
async / await (ES2017)
비동기 작업이 필요한 함수 앞에 async를 붙이고 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 된다.
Promise ~ then과 동일한 효과
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function(){
resolve(name);
}, 500);
});
};
//var coffeeMaker = async () => { : 화살표 함수 시 괄호 앞에
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
await _addCoffee('에스프레소'); //얘가 끝나길 밑의 코드가 기다림.
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
오늘은 콜백함수에 대해서 공부를 했다. 콜백에 대해서 진하게 공부하니까 코드 가독성과 유지보수 측면, 그리고 비동기와 동기에 대해서 이해하면서 서버 통신때 효율적이고 좋은 코드를 짜는 법에 대해 공부하게 된 것같아서 뿌듯하고 조금 힘들었다 ㅎㅎ..
그리고 예상치 못하게 알고리즘 푸는 것에 은근히 시간이 많이 사용되어서 계획했던 강의를 좀 더 늦게 수강하였다. 하지만 그럼에도 오늘안에 하고자 한 것을 다 해냈다는게 중요한 것같다!! 내일은 개인 프로젝트에 많은 시간을 소요할 예정이다. 아마 좀 더 욕심내서 주말을 사용해서 만들 예정이다. 주말에는 TIL 내용을 정리해서 따로 포스팅하고 개인 프로젝트 기능 구현도 정리해서 올려야겠다.