JavaScript-클로저

치맨·2023년 1월 19일
0

javascript

목록 보기
9/23
post-thumbnail

목차


우선 클로저를 공부하기 전에 다른 분들의 글을 읽어도 좋고, 저의 글을 읽어도 좋으니 먼저 스코프, 실행 컨텍스트를 먼저 이해하는게 저는 클로저를 이해하는 데 훨씬 도움이 됐었기 때문에 추천해 드립니다👍.
스코프 ←
실행 컨텍스트 ←

클로저란

  • 클로저란 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.

  • 조금 더 쉽게 외부 함수보다 중첩(내부) 합수가 더 오래 유지되는 경우 중첩(내부) 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있는데 이러한 중첩함수를 클로저 라고 부릅니다.

  • 즉 클로저는 아래의 조건을 만족하는 중첩함수입니다.

    • 상위 스코프의 변수를 참조
    • 본인의 외부 함수보다 오래 살아있는 경우

    이때 클로저에 의해 참조되는 상위 스코프의 변수를 자유변수 라고 합니다.

  • 좀 더 확실히 이해하기 위해 소소한 퀴즈를 해보겠습니다.

아래의 그림은 클로저일까요? 클로저가 아닐까요?

  1. 중첩함수인가? => father 함수는 global()안의 중첩함수 입니다. (O)
  2. 상위 스코프의 변수를 참조하고 있는가? => 참조하고 있지 않습니다.(X)
    ------이미 여기서 클로저는 아니라고 판단이 되지만, 3번째도 확인 해보겠습니다.
  3. 본인의 외부 함수보다 오래 살아있는 경우인가? =>오래살아있지 않습니다.
    따라서 father 함수는 클로저가 아닙니다.

아래의 그림은 클로저일까요? 클로저가 아닐까요?

  1. 중첩함수인가? => father 함수는 global()안의 중첩 함수 입니다. (O)

  2. 상위 스코프의 변수를 참조하고 있는가? => 참조하고 있습니다.(O)

  3. 본인의 외부 함수보다 오래 살아있는 경우인가? =>오래살아있지 않습니다.

따라서 father 함수는 클로저였지만, 외부함수보다 생명주기가 짧기 때문에 일반적으로 클로저라고 하지 않습니다.

  • 이대로 가면 아쉬우니까 클로저인 함수를 확인 해보겠습니다.

  1. 중첩함수인가? => father 함수는 global()안의 중첩(내부)함수 입니다. (O)

  2. 상위 스코프의 변수를 참조하고 있는가? => 상위 스코프의 변수인 globalVar 변수를 참조하고 있습니다.(O)

  3. 본인의 외부 함수보다 오래 살아있는 경우인가? =>global함수(외부함수)는 father함수(내부함수)를 return하고 생명주기를 먼저 마감합니다. (O)

따라서 father함수는 클로저 입니다.


클로저 이해하기

  • 어떻게 상위 스코프가 사라졌는데, 상위스코프의 변수를 계속 참조 할 수 있었을까? 라는 의문이 생겼습니다.
  1. 우선 상위 스코프 부터 차근차근 이해해 봅시다.

    • 자바스크립트는 정적스코프(렉시컬 스코프)를 따릅니다.
    • 즉 호출된 위치가 아닌, 함수가 정의된 위치에서 상위 스코프를 결정합니다.
  2. 함수는 자신의 내부 슬롯에함수가 현재 정의된 실행 컨텍스트, 즉 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 상위 스코프로 저장 합니다.

    • 조금 어려운것 같아 사진과 함께 설명 해보겠습니다.
    • 아래의 사진에서 father함수가 정의되는 시점엔 현재 실행중인 실행 컨텍스트는 보라색인 global 실행 컨텍스트 입니다.
    • 따라서 father함수의 내부슬롯 에는 global 실행 컨텍스트의 렉시컬 환경을 상위 스코프로 저장합니다.

  1. 그렇다면 외부함수가 사라져도(콜스택에서 pop 된 경우) 내부함수는 내부슬롯에 외부함수의 렉시컬 환경을 저장해뒀기 때문에, 외부함수의 변수에 접근이 가능 합니다.

실행컨텍스트 콜스택에서의 pop(제거)은 실제로 사라지는것은 아니고, 실행컨텍스트가 참조를 하지 않는것입니다. 만약 아무도 참조하지 않는다면 가비지 컬렉터에 의해 실제로 제거가 됩니다.


클로저의 활용

  • 상태(state)를 안전하게 변경하고 유지하기 위해 클로저를 사용합니다.

  • 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용합니다.

클로저를 활용하지 않은 경우

  • save()를 통해 5,000원씩 저장을 하고 있었습니다.

  • 그러나 중간에서 해커가 money에 접근하여 0을 만들어 버렸습니다.

  • 아무것도 모르고 4번의 저축 총 20,000원인줄 알고 check()로 확인 해봤더니 5,000원 밖에 없었습니다.

  • money는 오직 withdrawal을 통해서만 출력이 되어야 하고, save를 통해서만 저축이 되어야 합니다.

  • 이를 방지하기 위해 즉 외부에서 상태값에 접근을 하지 못하도록 할때 클로져를 활용할 수 있습니다.


클로저를 활용한 경우

  • 은닉화 : 전역변수였던 money를 즉시 실행 함수의 지역변수 안으로 넣어서 외부에서 접근을 막을 수 있습니다. 따라서 money=0으로 누군가 상태값을 변경 할려고 시도해도 바뀌지 않습니다.

  • 즉시 실행함수를 통해 초기화는 처음 한번만:즉시 실행함수가 아닌 일반 함수 였다면 money가 계속 0으로 초기화가 되지만, 즉시실행함수를 통해 딱 한번만 실행하여, 처음을 제외하곤 money가 초기화 될 수 없습니다.

  • 클로저의 특성을 활용 : 객체리터럴의 {}는 코드블록이 아니므로 따로 스코프를 생성하지 않고, save, check, withdrawal는 즉시 실행함수의 실행컨텍스트 렉시컬 환경을 각각의 함수의 내부슬롯에 저장하기 때문에, 즉시실행 함수의 실행컨텍스트가 없어져도, save, check, withdrawal 함수가 호출이 될때마다 money 변수를 참조 할 수 있습니다.


Ref

profile
기본기가 탄탄한 개발자가 되자!

0개의 댓글