[JavaScript] 클로저(Closure)

SeokHun·2021년 1월 9일
0

JavaScript

목록 보기
2/3

Closure를 이해하기 위해서는 Lexical Scope를 알아야 한다

Lexical Scope

변수의 유효범위(사용할 수 있는 범위)를 Lexical Scope라고 한다

아래 예시를 보면 sum 함수 안에서 연산을 실행하고 결과를 출력하는 함수를 호출하는데 이 함수는 sum의 지역변수인 result 변수를 사용할 수 있다

const sum = (a, b) => {
    let result = a + b;
    const displayResult = () => {
        console.log(result);
    }
    displayResult();
}

sum(1, 2);
sum(2, 2);

sum 함수를 통해 출력하는 결과들은 각자 다르다는 것을 확인 할 수 있다

결과 값 :
3
4

displayResult 함수가 sum 함수의 지역변수인 result에 접근할 수 있다

-> 함수(displayResult)의 선언된 위치가 sum 함수와 중첩되어 있어서 외부(sum) Lexical Scope에 접근할 수 있다는 뜻이다


! Lexical Scope는 Static Scope이라고도 한다 !
함수의 호출이 아니라 함수의 선언에 따라 결정된다
함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다

JavaScript가 Dynamic Scope를 따르고 있다면 (함수의 실행으로 상위 스코프가 결정된다면 ) 아래의 코드에서 sum(5+7)의 결과가 12, displayResult()의 결과가 1이여야 한다

let result = 1;

const sum = (a, b) => {
  let result = a + b;
  displayResult();
}

function displayResult(){
  console.log(result);
}

sum(5 + 7);
displayResult();

함수의 선언에 따라 상위 스코프가 결정되었기 때문에 결과값은 아래와 같다

1
1

Closure

MDN - Closure 에서는
함수와 함수가 선언된 Lexical(Static) 환경의 조합이라고 정의한다

정의를 보고 나서도 이해가 잘 되지 않아서 검색을 통해 알아보아 정리하였다

아래의 sum 함수에서 지역변수 result의 변수 값을 콘솔에 출력하는 함수를 반환하고 있다고 하자

const sum = (a, b) => {
    let result = a + b;
    return () => {
        console.log(result);
    }
}

let three = sum(1, 2);
let four = sum(2, 2);

감각적으로 three와 four 함수는 각자 다른 연산 값을 출력할 것이라고 예상할 수 있었고 결과 값도 아래와 같았다

3
4

이 때 반환된 함수에서 참조된 변수가 함수 실행이 끝났다고 해서 사라지지 않고 여전히 제대로 된 값을 반환하고 있는 걸 알 수 있다

반환된 함수(함수 내에서 함수를 정의하고 사용)를 클로저(Closure) 라고 한다

Closure가 함수와 함수가 선언된 Lexical(Static) 환경을 기억하고 있기 때문에 지역 변수를 무사히 참조할 수 있는 것이다


Closure Scope Chain

모든 클로저에는 세가지 스코프(범위)가 있다고 한다

  • 지역 범위 (Local Scope, Own scope)
  • 외부 함수 범위 (Outer Functions Scope)
  • 전역 범위 (Global Scope)

클로저에서 찾을 수 없는 변수를 찾을 때 상위 외부함수를 통해 변수를 찾게 되는데 이런 환경을 클로저 스코프 체인이라고 한다

다음과 같이 함수를 중첩으로 선언하고 외부 지역변수들과 전역변수까지 사용한다고 보자

let e = 10;
const sum = (a) => {
  return (b) => {
    return (c) => {
      return (d) => {
        // 지역 범위 (local scope)
        return a + b + c + d + e;
      }
    }
  }
}

console.log(sum(1)(2)(3)(4));

let s = sum(1);
let s1 = s(2);
let s2 = s1(3);
let s3 = s2(4);
console.log(s3);

결과 값은 아래와 같이 무사히 계산을 완료하는 것을 볼 수 있다.

20
20

따라서 모든 클로저들은 클로저 스코프 체인을 통해 세가지 범위의 변수들을 참조할 수 있고 모든 클로저가 선언된 외부 함수의 스코프에 접근한다라고 말할 수 있다


반복문 Closure

0-9 까지의 정수를 출력하는 코드를 만들고 싶어 아래와 같이 작성해봐도 출력은 원하는 대로 되지 않는다.

let i;

for (i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}

setTimeout()에서 실행되는 함수는 모두 0.1초 뒤에 호출된다

이 때 0.1초 뒤에는 반복문이 전부 돌아서 i가 10인 상태이므로 출력은 10이 될 것이다.

10
10
10
10
10
10
10
10
10
10

이를 정상적으로 출력시키려면 Closure를 이용하면 된다.

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}

혹은

let i;
for (i = 0; i < 10; i++) {
  ((x) => {
    setTimeout(() => {
    console.log(x);
  }, 100);
  })(i);
}

출력값

0
1
2
3
4
5
6
7
8
9

Closure의 성능

아래의 코드에서는 three 클로저가 계속 result 변수를 참조하고 있게 된다.

const sum = (a, b) => {
  const result = a + b;
  return () => {
    console.log(result);
  };
}

let three = sum(1, 2);

클로저를 통해 내부 변수를 참조하면 차지하는 메모리를 Garbage Collector(가비지 컬렉터)가 회수하지 않아서 클로저 사용이 끝나면 참조를 제거하는 것이 좋다고 한다

three();

0개의 댓글