[JavaScript 문법] 콜백함수

조아영·2024년 7월 29일

📌

콜백함수는 다른 코드의 인자로 넘겨주는 함수입니다. 인자로 넘겨준다는 말은 콜백함수를 넘겨받는 코드가 있다는 뜻입니다. 콜백 함수를 넘겨받은 코드 forEach, setTimeout 등은 콜백 함수를 필요에 따라 적절한 시점에 실행하게 됩니다.
callback = call(부르다) + back(되돌아오다) = 되돌아와서 호출해. 제어권을 넘겨줄테니 그 로직으로 처리해달라는 뜻 입니다.

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

// setTimeout
setTimeout(function () {
    console.log("Hello, world!");
}, 1000); // 1초 후 실행 : Hello, world!

// forEach
const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function (number) {
    console.log(number); // 1 2 3 4 5
});

◼ 제어권

1. 호출 시점

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

var count = 0;
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이 됩니다.

2. 인자

map 함수는 각 배열 요소를 변환하여 새로운 배열을 반환합니다. 기존 배열을 변경하지 않고, 새로운 배열을 생성합니다.

// map 함수로 새로운 배열 생성
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 ]

인자순서변경

// map 함수로 새로운 배열 생성
// currentValue, index 자리변경
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 ]

콜백함수에 넣은 currentValue, index변수의 자리를 바꿨을때 우리가 의도하지 않은 값이 나와버립니다.
index, currentValue라는 변수명은 사람이 이해하는 변수명일 뿐입니다. 컴퓨터는 두 변수를 자리의 위치(자리수)로 인식합니다.
이처럼, map 메서드를 호출해서 원하는 배열을 얻고자 한다면 정의된 규칙대로 작성해야 합니다. 이 모든것은 전적으로 map 메서드. 즉, 콜백 함수를 넘겨받은 코드에게 그 제어권이 있습니다. 인자(의 순서)까지도 제어권이 그에게 있는 것입니다. 제어권이 넘어갈 map 함수의 규칙에 맞게 호출해야 합니다.

3. this

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

// setTimeout은 내부에서 콜백 함수를 호출할 때, call 메서드의 첫 번째 인자에 전역객체를 넘김
// 따라서 콜백 함수 내부에서의 this가 전역객체를 가리킴
setTimeout(function () {
    console.log(this);
}, 300); // Window(global) { ... }

// forEach도 콜백 뒷 부분에 this를 명시해주지 않으면 전역객체를 넘김
[1, 2, 3, 4, 5].forEach(function (x) {
    console.log(this); // Window(global) { ... }
});

// 예외사항
// 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); // obj를 출력 : {vals:[1, 2, 3], logValuse: [Function: logValues]} 1 2

// forEach, map, filter는 매개변수로 (n, index)를 받음
// forEach의 매개변수로 obj.logValues 메서드를 넣은 것이 아니라,
// 메서드의 형태처럼 보이는 함수를 넣은 것임
// method로서 호출을 할때는 호출만 가능하고 매개변수로 넘겨줄 수 없음
// 즉, 여기서의 this는 전역객체임
[4, 5, 6].forEach(obj.logValues);
// ↑ 둘이 같음 ↓
[4, 5, 6].forEach(function (v, i) {
    console.log(this, v, i); // this는 Window(global) { ... }
});

◼ 내부의 this에 다른 값 바인딩

1. 전통적 방식

var obj1 = {
    name: "obj1",
    func: function () {
        var self = this;
        // 함수를 return하게 되어있음
        return function () {
            console.log(self.name);
        };
    },
};

// obj1.func()에서 ()는 실행 한 결과라는 뜻
var callback = obj1.func();
// ↑ 둘이 같음 ↓
var callback = function () {
    console.log(self.name);
};
setTimeout(callback, 1000);
// ↑ 둘이 같음 ↓
setTimeout(function () {
    console.log(self.name); // obj1
}, 1000);


var obj2 = {
    name: "obj2",
    func: function () {
        var self = this;
        return function () {
            console.log(self.name);
        };
    },
};
var callback2 = obj2.func();
// ↑ 둘이 같음 ↓
var callback2 = function () {
    console.log("obj2");
};
setTimeout(callback2, 1500);
// ↑ 둘이 같음 ↓
setTimeout(function () {
    console.log(self.name);
}, 1500); // obj2

var obj3 = { name: "obj3" };
// 즉시 실행 함수 call을 활용. obj1에 있는 func를 즉시 실행
// obj3를 this바인딩 할 수 있게 넘김
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000); // obj3

2. bind메서드의 활용

var obj1 = {
    name: "obj1",
    func: function () {
        console.log(this.name);
    },
};
// 함수 자체를 obj1에 바인딩
// obj1.func를 실행할 때 무조건 this는 obj1로 고정
setTimeout(obj1.func.bind(obj1), 1000); // obj1

var obj2 = { name: "obj2" };
// 함수 자체를 obj2에 바인딩
// obj1.func를 실행할 때 무조건 this는 obj2로 고정
setTimeout(obj1.func.bind(obj2), 1500); // obj2

0개의 댓글