[JS] 콜백함수의 제어권

호출 시점에 대한 제어권

// setInterval: 반복해서 매개변수로 받은 콜백함수의 로직을 수행
let count = 0;
let cdFunc = function() {
    console.log(count);
    if(++count > 4) clearInterval(timer)
    };

// setInterval의 콜백함수로 전달
let timer = setInterval(cbFunc, 300);

// 직접 호출
cbFunc();

마지막처럼 cbFunc()를 직접 호출하는 경우 함수의 호출 시점은 프로그래머가 제어할 수 있다. 하지만 setInterval의 콜백함수로 전달하는 경우, 300ms 간격으로 setInterval이 호출 시점을 제어하게 된다.

 

 

인자에 대한 제어권

let newArr = [10, 20, 30].map(function (currentValue, index) {
    console.log(currentValue, index);
    return currentValue + 5;
});

map과 같은 메서드를 사용하는 경우 정해진 규칙을 활용해야 한다. 내가 임의로 index를 첫번째 인자로 넘기고 싶다하더라도 제어할 수 없다. 따라서 인자에대한 제어권이 호출한 메서드 (map)에 있다는 것!

 

 

콜백함수의 this 바인딩

제어권을 넘겨받은 코드에서 콜백 함수에 별도로 this가 될 대상을 지정할 경우, 콜백함수 내부의 this는 그 대상을 참조한다!

 

Array.prototype.myMap = function(callback, thisArg) {
    let mappedArr = [];
    
    for(let i = 0; i < this.length; i++) {  // this는 myMap을 호출한 배열
        let mappedValue = callback.call(thisArg || global, this[i]);
        mappedArr[i] = mappedValue;
    }
    
    return mappedArr;
};

let newArr = [1, 2, 3].myMap(function(number) {
    return number * 2;
});

console.log(newArr);  // [2, 4, 6]

 

 

객체의 메서드를 콜백함수로 전달한 경우 this

const obj = {
    vals: [1, 2, 3],
    logValues: function(v, i) {
        console.log(this, v, i);
    },
};

obj.logValues(1, 2);  // obj 1 2

일반적으로 메서드를 호출하는 경우 호출한 주체(객체)가 this다.

 

const obj = {
    vals: [1, 2, 3],
    logValues: function(v, i) {
        if(this === global) {
            console.log("this가 global입니다.");
        } else {
            console.log(this, v, i);
        }
    },
};

[4, 5, 6].forEach(obj.logValues);  // this가 global입니다. (3번 출력)

forEach는 콜백함수에 요소의 값과 인덱스를 매개변수로 넘겨준다. 따라서 logValues의 매개변수로는 4, 0 / 5, 1 / 6, 2가 전달된다.

obj.logValues의 형태때문에 obj가 호출의 객체라고 생각하기 쉽지만 이는 obj.logValues의 값인 function(v, i) { }를 가져온 것일 뿐이다! (호출하지 않음) 따라서 obj.logValues는 함수로서 독립적으로 실행된 것이고 (특정 객체의 메서드로 호출된 것이 아님) global을 this로 갖게된다.

 

 

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

const obj1 = {
    name: "obj1",
    func: function () {
        console.log("this: ", this);
        console.log("this.name: ", this.name);
    },
};

setTimeout(obj1.func, 1000);  // Timeout 객체, undefined

위와 같이 실행하는 경우 1초 후 obj1의 이름을 출력하지 않는다! obj1이 호출의 주체가 아니기 때문이다. 따라서 원하는 기능을 위해 bind()를 이용해 호출 전 this를 obj1로 바인딩해줄 수 있다. bind만 가능한 것은 아니다!

 

const obj1 = {
    name: "obj1",
    func: function () {
        console.log("this: ", this);
        console.log("this.name: ", this.name);
    },
};

const boundObj1 = obj1.func.bind(obj1);
setTimeout(boundObj1, 1000);  // obj1 객체, obj1

const obj2 = {
    name: "obj2",
};

setTimeout(obj1.func.bind(obj2), 1000);  // obj2 객체, obj2