클로저(Clouser)

kim yeseul·2023년 10월 23일
0

Javascript

목록 보기
6/8
post-thumbnail
post-custom-banner

자바스크립트에서 클로저 문제가 발생하는 경우?

스코프, 비동기, var, for문(반복문)과 비동기를 함께 사용하면 종종 발생하게 된다.

대표 예시

function test() {
  for(var i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000)
  }
}

test();

// 내가 기대한 출력값 0 1 2 3 4
// 실제 출력값 5 5 5 5 5

🧐 왜 5 5 5 5 5 가 나올까?

1. var의 사용

var함수 스코프 변수(function-scope)를 생성한다. 따라서 for문 내에서 var로 선언된 변수 i는 사실상 함수 전체에서 접근이 가능하다. 이것은 setTimeout 콜백 함수 내에서 i에 접근할 때, 이미 반복이 끝난 후의 i값을 참조하게 된다.
** var는 for문 안에 있는 것이 아닌 함수 안에 있는 것이다.

2. 비동기 작업과 이벤트 루프

setTimeout은 비동기로 작동하는 함수이므로 즉시 실행되지 않고 백그라운드에서 실행되고 지정된 시간이 경과한 후 콜백 함수를 호출한다. setTimeout 콜백 함수는 반복문이 이미 완료된 후에 호출된다. 따라서 모든 콜백 함수는 같은 i 변수를 참조하게 되고, 그 시점에 i는 5가 된다.

3. 클로저

클로저란 함수 외부에 있는 변수와의 관계이다. 내부함수가 외부함수의 접근할 수 있는 변수나 매개변수에 접근하는 것을 말한다. 위 코드에서 setTimeout 콜백 함수는 사실상 test() 함수의 내부 함수다.

클로저 관점에서 위 예시 코드가 어떻게 작동하는지 알아보자.

  1. 루프가 시작되면 i 값은 0부터 4까지 증가한다.
  2. 각 루프마다 setTimeout을 설정하며, 콜백은 지정된 시간 (i * 1000ms) 후에 실행된다.
  3. 단, setTimeout은 비동기 로직이므로 즉시 실행되지 않는다.
  4. 모든 루프가 종료된 후(i가 이미 5인 상태) 각각의 setTimeout 콜백들이 차례대로 호출된다.
  5. 이때 각각의 콜백들은 자신을 생성한 test() 함수 스코프에서 i 값을 찾아 콘솔에 출력한다.
  6. setTimeout 루프 시에 이미 i가 5인 상태이므로 모든 결과값이 5로 출력되는 것이다.

어떻게 해결할까?

1. let으로 변경해준다.

➡️ let은 ES6에서 추가된 블록 단위 스코프(block scope)를 가지는 키워드로 각 반복마다 고유한 스코프를 가진 변수(i)를 생성한다.

function solved1() {
  for(let i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000)
  }
}

2. 즉시 실행 함수를 사용한다.

➡️ 즉시 실행 함수(IIFE, Immediately Invoked Function Expression)를 사용하면, 각 반복마다 새로운 스코프가 생성되어 i의 현재 값을 고정하는 효과를 얻을 수 있다.

➡️ (function(j) { // 코드 })() 함수는 정의되자마자 바로 호출된다. 이 경우, i의 현재 값이 즉시 실행 함수에 인자로 전달되어 그 값이 각각의 함수 스코프 내에 고정된다.

➡️ 아래의 (function(j) { })(i) 부분은 i값을 받아서 즉시 실행하는 함수다. 이와 같이 사용하면 각 반복마다 j라는 이름의 새 변수가 생성되고 해당 변수는 자신만의 스코프 내에서 setTimeout에 의해 만들어진다. 즉, setTimeout이 실제로 호출될 때 각각의 콜백이 참조하는 j 값은 그 시점에서의 i 값으로 고정된다.

function solved2() {
  for(var i = 0; i < 5; i++) {
    (function(j) {
      setTimeout(() => {
        console.log(j);
      }, i * 1000)
    })(i);
  }
}

// 즉시 실행 함수가 외부 변수인 var i와 클로저 관계를 형성하고 있는 것
(function(j) {
  setTimeout(() => {
    console.log(j);
  }, i * 1000)
})(i);
 
// setTimeout 내부의 함수가 외부 변수인 j와 클로저 관계를 형성하고 있는 것 
() => {
  console.log(j);
}
profile
출발선 앞의 준비된 마음가짐, 떨림, 설렘을 가진 주니어 개발자
post-custom-banner

0개의 댓글