Skip to main content

Command Palette

Search for a command to run...

[4장] 콜백 함수

Updated
12 min readView as Markdown
[4장] 콜백 함수
W

안녕하세요! 프론트엔드 개발자 woodstock입니다.

저는 매일 조금씩 발전하고자 하는 마음으로 개발공부를 시작했고, 이 블로그는 그 과정에서 배우고 성장하는 이야기를 담고 있습니다.

여러분의 피드백과 조언은 언제나 환영합니다! 함께 배우고 성장하는 과정을 즐길 수 있기를 기대합니다.

1. 콜백함수란?

콜백 함수는 다른 코드의 인자로 넘겨주는 함수이다. 콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행할 것이다.

예를 들면, 어떤 함수 X를 호출하면서 '특정 조건일 때 함수 Y를 실행해서 나에게 알려달라'는 요청을 함께 보내는 것이다. 이 요청을 받은 함수 X의 입장에서는 해당 조건이 갖춰졌는지 여부를 스스로 판단하고 Y를 직접 호출한다.

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

2. 제어권

2-1 호출 시점

콜백 함수 예제 (1-1) setInterval

var count = 0;
var timer = setInterval(function () {
  console.log(count);
  if (++count > 4) clearInterval(timer);
}, 300);
  • setInterval을 호출할 때 두 개의 매개변수를 전달했는데, 그중 첫 번째는 익명 함수이고 두 번째는 300이라는 숫자이다.

setInterval의 구조는 다음과 같다.

var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);
  • func: 함수
  • delay : 밀리초(ms) 단위의 숫자
  • param1, param2, ... : func함수를 실행할 때 매개변수로 전달할 인자

scope에는 Window객체 또는 Worker의 인스턴스가 들어올 수 있다. 두 객체 모두 setInterval메서드를 제공하기 때문이다. 일반적인 브라우저 환경에서는 window를 생략해서 함수처럼 사용이 가능하다.

func에 넘겨준 함수는 매 delay마다 실행되며, 그 결과 어떠한 값도 리턴하지 않는다. setInterval을 실행하면 반복적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID값이 반환된다. 이를 변수에 담는 이유는 반복 실행되는 중간에 종료(clearInterval)할 수 있게 하기 위해서이다.

콜백 함수 예제 (1-2) setInterval

var count = 0;
var cbFunc = function () {
  console.log(count);
  if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);

// -- 실행 결과 --
// 0  (0.3초)
// 1  (0.6초)
// 2  (0.9초)
// 3  (1.2초)
// 4  (1.5초)
  • timer변수에는 setInterval의 ID값이 담긴다.
  • setInterval에 첫 번째 인자로 전달한 콜백 함수 cbFunc는 0.3초마다 자동으로 실행된다. 이 때, 내부에서는 count값을 출력하고, count를 1만큼 증가시킨 다음, 그 값이 4보다 크면 반복 실행을 종료하라고 한다.

cbFunc의 제어권을 넘겨받은 setInterval이 스스로의 판단에 따라 적절한 시점에(0.3초마다) 이 익명 함수를 실행한 것이다. 이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다. 이미지 출처: "코어 자바스크립트", 정재남, 위키북스, 2019, p. 2.


2-2 인자

우선 예제를 보기전에 map의 구조를 먼저 알아보자

Array.prototype.map(callback[, thisArg])
callback: function(currentValue, index, array)
  • currentValue : 배열의 요소 중의 현재값
  • index : 현재값의 인덱스
  • array : map메서드의 대상이 되는 배열 자체

map메서드는 첫 번째 인자로 callback함수를 받고, 생략 가능한 두 번째 인자로 콜백 함수 내부에서 this로 인식할 대상을 특정할 수 있다. thisArg를 생략할 경우에는 일반적인 함수와 마찬가지로 전역객체가 바인딩 된다.

콜백 함수 예제 (2-1) Array.prototype.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]
  • [10, 20, 30]map메서드를 호출하고, 그 첫 번째 매개변수로는 익명함수를 전달했다. 이제 배열 [10, 20, 30]의 각 요소를 처음부터 하나씩 꺼내어 콜백 함수를 실행한다.
  • 첫 번째 요소에 대한 콜백 함수는 currentValue에 10, index에 0이 담긴 채 실행되고, 각 값을 출력한 다음 15(10 + 5)를 반환한다.
  • 두 번째 요소에 대한 콜백 함수는 currentValue에 20, index에 1이 담긴 채 실행되고, 각 값을 출력한 다음 25(20 + 5)를 반환한다.
  • 같은 방식으로 세 번째에 대한 콜백 함수까지 실행을 마치고 나면 [15, 25, 35]라는 새로운 배열이 만들어져 변수 newArr에 담기고, 이 값이 출력된다.

제이쿼리의 메서드들은 기본적으로 첫 번째 인자에 index가, 두 번째 인자에 currentValue가 온다. 만약 map메서드를 제이쿼리의 방식처럼 순서를 바꾸어 사용해보면 어떨까?

콜백 함수 예제 (2-2) Array.prototype.map - 인자의 순서를 임의로 바꾸어 사용한 경우

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라고 명명했지만, 순서상으로 두 번째이기 때문에 현재값의 인덱스가 배정된다. 그렇기때문에 [5, 6, 7]이라는 결과가 나오게 되는 것이다.

map메서드를 호출해서 원하는 배열을 얻으려면 map메서드에 정의된 규칙에 따라 함수를 작성해야 하며, 그 규칙에는 콜백 함수의 인자로 넘어올 값들 및 그 순서도 포함되어 있다. 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지는 전적으로 map메서드에 달린 것이다.

이처럼 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.


2-3 this

이전 챕터인 this에서 콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 되는 것을 알아보았다.

해당 내용을 인지하고 다음 예제를 살펴보자.

콜백 함수 예제 (2-3) Array.prototype.map - 구현

01 Array.prototype.map = function(callback, thisArg) {
02   var mappedArr = [];
03   for (var i = 0; i < this.length; i++) {
04     var mappedValue = callback.call(thisArg || window, this[i], i, this);
05     mappedArr[i] = mappedValue;
06   }
07   return mappedArr;
08 };
  • 4번째 줄에서 callback.call을 호출하고, 그 반환값은 mappedValue에 담긴다.
    • this에는 thisArg값이 있을 경우 그 값을, 없을 경우에는 전역객체를 지정한다.
    • 첫 번째 인자에는 메서드의 this가 배열을 가리킬 것이므로 배열의 i번째 요소 값을,
    • 두 번째 인자에는 i의 값을,
    • 세 번째 인자에는 배열 자체를 지정한다.
  • 5번째 줄에서 mappedValue가 변수mappedArri번째 인자에 할당된다.

제어권을 넘겨받을 코드에서 call / apply메서드의 첫 번째 인자에 콜백 함수 내부에서 this가 될 대상을 명시적으로 바인딩하기 때문에 this에 다른 값이 담기는 것을 알 수 있다.

콜백 함수 내부에서의 this

setTimeout(function() {
  console.log(this);
}, 300); // (1) Window { ... }

[1, 2, 3, 4, 5].forEach(function(x) {
  console.log(this); // (2) Window { ... }
});

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener(
  'click',
  function(e) {
    console.log(this, e); // (3) <button id="a">클릭</button>
  } // MouseEvent { isTrusted: true, ... }
);
  • (1) : setTimeout에서 콜백 함수는 일반 함수로서 호출되기 때문에 this는 전역 객체를 가리키게 된다.
  • (2) : forEach는 기본적으로 콜백 함수의 this값을 전역 객체로 설정하며, 선택적인 두 번째 인자로 콜백 함수 내에서 this로 사용될 값을 지정할 수 있다. 예제에서는 별도로 this를 지정해주지 않았기 때문에 전역 객체를 가리키게 된다.
  • (3) : addEventListener는 이벤트 처리할 때 사용되는 콜백 함수 내의 this값을 이벤트 리스너가 부착된 DOM 요소로 자동 설정한다. 이는 addEventListener 기본 동작이며, 결과적으로 콜백 함수 내에서 this는 이벤트가 발생한 특정 DOM 요소를 가리키게 된다.



3. 콜백 함수는 함수다

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

메서드를 콜백 함수로 전달한 경우

01 var obj = {
02   vals: [1, 2, 3],
03   logValues: function(v, i) {
04     console.log(this, v, i);
05   },
06 };
07 obj.logValues(1, 2); // { vals: [1, 2, 3], logValues: f } 1 2
08 [4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0
09 // Window { ... } 5 1
10 // Window { ... } 6 2
  • 7번째에서는 logValues를 메서드로서 호출했기 때문에 thisobj를 가리키고, 인자로 넘어온 1, 2가 출력된다.
  • 8번째에서는 logValuesforEach함수의 콜백 함수로서 전달했다.
    • 이때 objthis로 하는 메서드를 그대로 전달한 것이 아니라, obj.logValues가 가리키는 함수만 전달한 것이다.
    • 따라서 forEach에 의해 함수로서 호출되고, 별도로 this를 지정하는 인자가 ㅇ벗으므로 함수 내부에서의 this는 전역객체를 바라보게 된다.

즉, 어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수일 뿐인 것이다.



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

콜백 함수 내부에서 this가 객체를 바라보게 하려면 어떻게 해야 할까?

별도의 인자로 this를 받는 함수는 원하는 값을 넘겨주면 되지만, 그렇지 않은 경우에는 this의 제어권도 넘겨주게 되므로 사용자가 임의로 값을 바꿀 수 없다.

그래서 전통적으로는 this를 다른 변수에 담아 콜백 함수로 활용할 함수에서는 this대신 그 변수를 사용하게 하고, 이를 클로저로 만드는 방식이 많이 쓰였다.

콜백 함수 내부의 this에 다른 값을 바인딩하는 방법(1) - 전통적인 방식

var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this;
    return function() {
      console.log(self.name);
    };
  },
};
var callback = obj1.func();
setTimeout(callback, 1000);  // 1초 뒤 'obj1' 출력
  • obj1.func메서드 내부에서 self변수에 this를 담고, 익명 함수를 선언과 동시에 반환한다.
  • obj1.func를 호출하면 앞서 선언한 내부함수가 반환되어 callback변수에 담긴다.
  • callbacksetTimeout함수에 인자로 전달하면 1초 뒤 callback이 실행되면서 obj1을 출력한다.

이 방식은 실제로 this를 사용하지도 않을뿐더러 번거로워 보인다.


그렇다면, 차라리 아래와 같이 this를 아예 안 쓰는 편이 더 낫지 않을까?

콜백 함수 내부에서 this를 사용하지 않은 경우

var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(obj1.name);
  },
};
setTimeout(obj1.func, 1000);

물론, 이코드는 훨씬 간결하고 직관적이지만 this를 이용해 다양한 상황에 재활용하는 것은 불가능하다.


다음과 같이 다른 객체에 재활용하는 상황을 살펴보자.

앞선 예제(전통적인방식)func함수 재활용

var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this;
    return function() {
      console.log(self.name);
    };
  },
};

var obj2 = {
  name: 'obj2',
  func: obj1.func,
};
var callback2 = obj2.func();
setTimeout(callback2, 1500); // 1.5초 후 'obj2' 출력

var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000); // 2초 후 'obj3' 출력

이처럼 앞선 예제(전통적인 방식)는 번거롭긴 하지만 this를 우회적으로나마 활용함으로써 다양한 상황에서 원하는 객체를 바라보는 콜백 함수를 만들 수 있는 방법이다.

반면 콜백 함수 내부에서 this를 사용하지 않는 예제의 경우는 처음부터 바라볼 객체를 명시적으로 obj1으로 지정했기 때문에 다른 객체를 바라보게 할 수 없다. 그렇기때문에 메모리를 낭비하는 단점이 있음에도 전통적인 방식이 널리 통용될 수 밖에 없었던 것이다.


이러한 전통적인 방식의 단점은 이후 ES5에서 나온 bind메서드로 보완할 수 있다.

콜백 함수 내부의 this에 다른 값을 바인딩하는 방법(2) - bind 메서드 활용

var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(this.name);
  },
};
setTimeout(obj1.func.bind(obj1), 1000);  // 1초 후 'obj1' 출력

var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500);  // 1.5초 후 'obj2' 출력



5. 콜백 지옥과 비동기 제어

콜백 지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 과도하게 깊어지는 현상을 의미한다.

비동기는 동기의 반대개념이다.

동기적인 코드는 현재 실행 중인 코드가 완료된 후 다음 코드를 실행하는 방식이고, 비동기는 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.

즉, CPU의 계산에 의해 즉시처리가 가능한 대부분의 코드는 동기적이고, setTimeout, addEventListener, XMLHttpRequest와 같이 별도의 요청, 실행 대기, 보류에 관련된 코드는 비동기적이라고 할 수 있다.

이전에 콜백 지옥비동기에 관련된 내용을 정리해 두었으니, 참고해보면 좋을 것 같다.


현대의 자바스크립트는 웹의 복잡도가 높아진 만큼 비동기적인 코드의 비중이 훨씬 높아진 상황이다. 그리고 그만큼 콜백 지옥에 빠지기도 훨씬 쉬워졌다.

콜백 지옥 예시 (1-1)

setTimeout(
  function(name) {
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(
      function(name) {
        coffeeList += ', ' + name;
        console.log(coffeeList);

        setTimeout(
          function(name) {
            coffeeList += ', ' + name;
            console.log(coffeeList);

            setTimeout(
              function(name) {
                coffeeList += ', ' + name;
                console.log(coffeeList);
              },
              500,
              '카페라떼'
            );
          },
          500,
          '카페모카'
        );
      },
      500,
      '아메리카노'
    );
  },
  500,
  '에스프레소'
);

// 0.5초 간격으로 출력된 결과
// 에스프레소
// 에스프레소, 아메리카노
// 에스프레소, 아메리카노, 카페모카
// 에스프레소, 아메리카노, 카페모카, 카페라떼
  • 0.5초 주기마다 커피 목록을 수집하고 출력한다.
  • 각 콜백은 커피 이름을 전달하고 목록에 이름을 추가한다.

목적 달성에는 지장이 없지만 들여쓰기 수준이 과도하게 깊어지고 값이 전달되는 순서가 '아래에서 위로' 향하고 있어 어색하게 느껴진다.


익명의 콜백 함수를 기명함수로 전환하면 위와 같은 문제를 해결할 수 있다.

콜백 지옥 해결 - 기명함수로 전환

var coffeeList = '';

var addEspresso = function(name) {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function(name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, '에스프레소');

이제 코드의 가독성이 향상되어 '위에서 아래로' 읽는것이 쉬워졌다.

하지만 이렇게 일회성 함수를 모두변수에 할당하면, 코드명을 일일이 따라다녀야 하므로 오히려 혼란을 초래할 수 있다.


이러한 비동기 작업의 혼란을 줄이고자 자바스크립트 진영은 ES6에서 Promise, Generator를, ES2017에서 async/await를 도입했다.

Promise

new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만, 그 내부에 resolve 또는 reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then또는 catch로 넘어가지 않는다.

따라서 비동기 작업이 완료될 때 비로소 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능하다.

비동기 작업의 동기적 표현(1) - Promise(1)

new Promise(function(resolve) {
  setTimeout(function() {
    var name = '에스프레소';
    console.log(name);
    resolve(name);
  }, 500);
})
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 아메리카노';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페모카';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function(prevName) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        var name = prevName + ', 카페라떼';
        console.log(name);
        resolve(name);
      }, 500);
    });
  });


위 예제의 반복적인 내용을 함수화하면 더 간결하게 표현할 수 있다.

비동기 작업의 동기적 표현(2) - Promise(2)

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('카페모카'))
  .then(addCoffee('카페라떼'));
  • addCoffee 함수
    • name을 인자로 받으며, 또 다른 함수를 반환한다.
    • 반환된 함수는 prevName을 인자로 받는다.
    • 반환된 함수 내부에서는 new Promise를 사용하여 비동기 작업을 구현하며, 비동기 작업은 setTimeout으로 지연되어 실행된다.
  • setTimeout과 비동기 작업
    • 내부의 익명 함수는 prevName이 존재하는지 확인하고 있다면prevNamename을 결합하여 이를 newName에 저장, 없다면 namenewName에 저장한다.
    • 이렇게 생성된 새 이름 목록을 newName변수에 저장하고, 콘솔에 출력한다.
  • Promise의 resolve 함수
    • setTimeout 내부의 작업이 완료되면, resolve(newName)에 의해, .then메소드에 제공된 다음 함수에 newName이 인자로 전달된다.


Generator

Generator를 실행하면 Iterator가 반환되는데, Iteratornext라는 메서드를 가지고 있다.

next메서드를 호출하면 Generator함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다. 이후 다시 next메서드를 호출하면 앞서 멈췄던 부분에서 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춘다.

비동기 작업이 완료되는 시점마다 next메서드를 호출하면 Generator함수 내부의 소스가 위에서부터 아래로 순차적으로 진행되는 것이다.

비동기 작업의 동기적 표현 (3) - Generator

01 var addCoffee = function(prevName, name) {
02   setTimeout(function() {
03     coffeeMaker.next(prevName ? prevName + ', ' + name : name);
04   }, 500);
05 };
06 var coffeeGenerator = function*() {
07   var espresso = yield addCoffee('', '에스프레소');
08   console.log(espresso);
09   var americano = yield addCoffee(espresso, '아메리카노');
10   console.log(americano);
11   var mocha = yield addCoffee(americano, '카페모카');
12   console.log(mocha);
13   var latte = yield addCoffee(mocha, '카페라떼');
14   console.log(latte);
15 };
16 var coffeeMaker = coffeeGenerator();
17 coffeeMaker.next();
  • 6번째 줄의 *이 붙은 함수가 Generator함수이다.
  • coffeeGenerator를 실행하면 next라는 메서드를 가지고 있는 coffeeMaker가 반환된다.
  • coffeeMaker.next()를 호출하면 Generator함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
  • addCoffee함수는 setTimeout을 수행하고, 이 작업이 완료될 때마다 coffeMaker.next()를 호출한다. 그리고 Generator함수가 다시 실행되어 다음 yield키워드까지 이동한다.
  • 순차적 실행 과정
    • coffeeMaker.next() 호출로 '에스프레소'가 추가되고, setTimeout이 완료된 후 다음 next() 호출로 '아메리카노'가 추가된다.
    • 이 패턴이 '카페모카', '카페라떼'에 대해서도 반복된다.
    • 결과적으로, 에스프레소, 에스프레소, 아메리카노, 에스프레소, 아메리카노, 카페모카, 에스프레소, 아메리카노, 카페모카, 카페라떼 순서로 커피 목록이 만들어지고 출력된다.


async/await

비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기하면 뒤의 내용이 Promise로 자동 전환되고, 해당 내용이 resolve된 이후에야 다음으로 진행한다.

Promisethen과 흡사한 효과를 얻을 수 있다.

비동기 작업의 동기적 표현 (4) - Promise + async/await

var addCoffee = function(name) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(name);
    }, 500);
  });
};
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();



6. 정리

  • 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.

  • 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.

    • 콜백 함수를 호출하는 시점을 스스로 판단해서 실행한다.
    • 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있다.
      이 순서를 따르지 않고 코드를 작성하면 엉뚱한 결과를 얻게 된다.
    • 콜백 함수의 this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있다.
      정하지 않은 경우에는 전역객체를 바라본다.
      사용자 임의로 this를 바꾸고 싶을 경우 bind메서드를 활용하면 된다.
  • 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.

  • 비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠지기 쉽다.

    • 최근의 ECMAScript에는 Promise, Generator, async/await 등 콜백 지옥에서 벗어날 수 있는 방법들이 등장하고 있다.

코어 자바스크립트

Part 8 of 14

이 시리즈에서는 '코어 자바스크립트'를 통해 자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다. 기본 이론들 중에서 ES5와 ES6를 아우르는 중요성이 높은 핵심 개념을 위주로 다루며, 테크닉이나 요령보다는 원리를 이해하는 데 목적을 두고 학습합니다.

Up next

[3장] this - 북 스터디

코딩 마을 방범대 북 스터디 DAY 3

More from this blog

💥 new Array(length).map()으로 배열 초기화 시 콜백이 실행되지 않는 문제

JavaScript에서 배열을 초기화하려고 new Array(4).map(() => [])을 사용했는데 의도대로 동작하지 않았다. 원인을 찾아보니 JavaScript의 빈 슬롯과 undefined의 차이, 그리고 map() 메서드가 빈 슬롯에 대해 콜백을 실행하지 않는다는 특성때문이었다. 문제 상황 특정 크기의 배열을 만들고 각 요소를 []로 초기화 하려고 했다. const result = new Array(4).map(() => []); co...

Jun 28, 20252 min read17

💥 에러 메시지와 스택 트레이스 분석을 통해 문제 해결하기

JavaScript로 카드 게임 로직을 구현하던 중 TypeError: Cannot read properties of undefined라는 에러를 만났다. 코드가 복잡하고 짐작가는 부분이 단번에 떠오르지 않아 막막했는데, 에러 메세지와 스택 트레이스를 차근차근 분석해가며 원인을 찾아낸 과정을 기록해본다. 에러 발생 게임 로직을 테스트하던 중 특정 입력에서 다음과 같은 에러가 발생했다. TypeError: Cannot read properties...

Jun 25, 20254 min read11

🧐 적절한 함수명과 단일 책임으로 코드 개선하기

이 글에서는 JavaScript로 구현된 보드게임의 로직을 개선하는 과정을 다루며, 개선 과정은 함수명 개선 → 중복 제거 → 책임 분리의 단계로 이루어져있다. 작동하지만 개선이 필요한 코드 처음에 작성한 코드는 기능적으로는 문제없이 동작했지만, 더 나은 방식을 고민하다 보니 여러 개선점을 찾을 수 있었다. function checkLadder(position) { switch (position) { case 4: return 14;...

Jun 23, 20253 min read23

작심삼십일 - TypeScript 강의를 완강하다.

유데미 작심삼십일 챌린지에 참여하다 작심삼십일 챌린지는 유데미의 한국 런칭 3주년을 기념한 이벤트 중 하나로, 30일 동안 유데미 강의를 들으며 인증을 하는 챌린지이다.취업 준비를 하면서 여러가지 요인으로(주로 심리적이지만..) 학습 루틴이 망가졌던 찰나에 이 이벤트를 보게되었고, 마침 기존에 사두고 볼 엄두가 안 났던 타입스크립트 강의가 있었기에 옳다구나! 하고 신청했었다. 챌린지를 통해 무엇을 배웠나? 챌린지의 목표로 정한 강의는 유데미에...

Nov 18, 20242 min read58
작심삼십일 - TypeScript 강의를 완강하다.

woodstock's devlog

215 posts

안녕하세요! 프론트엔드 개발자 woodstock입니다.

제 블로그에서는 프론트엔드 개발에 대한 다양한 주제를 다룹니다.

JavaScript, TypeScript, React, Next.js 등의 기술을 활용하여 실제 프로젝트를 진행하면서 얻은 경험과 지식을 공유하고자 하며, 컴퓨터 과학(CS)의 기본 개념과 알고리즘, 자료구조 등에 대해서도 다룹니다.