(1) 콜백함수란?

  1. 다른 코드의 인자로 넘겨주는 함수. forEach, setTimeout등이 있습니다.

  2. 넘겨받는 코드 ( forEach, setTimeout )에게 제어권이 있습니다.

  3. callback = call + back → 되돌아와서 호출해줘!

    <aside> 💡 다시 말하면, 제어권을 넘겨줄테니 너가 알고 있는 그 로직으로 처리해줘!

    </aside>

  4. 즉, 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수. 콜백 함수를 위임받은 코드는 **자체적**으로 **내부 로직**에 의해 이 콜백 함수를 적절한 시점에 실행

(2) 제어권

콜백함수를 넘겨받은 코드는 어떠한 제어권을 갖게 될까?

  1. 호출 시점

    → 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

    → 언제 콜백함수를 호출할지에 대한 제어권을 가지게 되는것.

    → cbFunc()를 호출하게 된다면 호출 주체와 제어권은 모두 사용자

    → setInterval로 넘겨주게 되면 호출주체와 제어권은 setInterval이된다.

    code 호출 주체 제어권
    cbFunc(); 사용자 사용자
    setInterval(cbFunc, 300); setInterval setInterval
    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)
    
  2. 인자

    → 인자의 순서까지도 제어권이 콜백함수를 넘겨받는 코드에 있다.

    → 예시

    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 ]
    

    콜백함수의 인자인 currentVlaue, index의 순서를 바꾸면 작동하지 않는다.

    위 예시에서 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 ]
    
  3. this

    → this는 기본적으로 함수에서는 전역객체를 참조하지만 예외사항이 있다. 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.

    → map함수를 직접 구현해보기

    this를 명시적 바인딩하기 때문에 this에 다른 값이 담길 수 있는것.

    // 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); 
    -> [2, 4, 6]
    
    //이젠 이 코드를 좀 더 잘 이해할 수 있어요!!
    
    // 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);
    });
    

(3) 콜백 함수는 함수다

콜백 함수로 어떤 객체의 메소드를 전달하더라도, 그 메소드는 메소드가 아닌 함수로 호출을한다.

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);

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

🤔 콜백 함수 내부에서 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);

→ 위 코드를 재활용 하는 방법

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);

→ 가장 좋은 방법

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);

(5) 콜백 지옥과 비동기 제어

  1. 콜백 지옥이란?
    1. 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 들여쓰기가 헬 수준인 경우

    2. 주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업 시 발생.

    3. 가독성, 수정 어려움

      Untitled