[JS] 클로저(Closure)

김민규·2022년 7월 1일
0

자바스크립트

목록 보기
1/7
post-thumbnail

참고한 페이지

  1. MDN 클로저
  2. 변수의 유효범위와 클로저
function greeting() {
  const name = 'Jason';
  
  function hello() { 
    console.log(`Hello, ${name}!`);
  }
  hello();
}

greeting();

위 코드에서 hello 함수는 자신의 외부에 있는 name에 접근합니다. greeting 함수를 실행하면 'Hello, Jason!'을 출력하죠. 예제를 조금 바꿔보겠습니다.

function greeting() {
  const name = 'Jason';
  
  function hello() {
    console.log(`Hello, ${name}!`);
  }
  return hello;
}

let helloFunc = greeting();
helloFunc();

greeting 함수는 hello 함수를 반환합니다. helloFunc 실행하면 'Hello, Jason!'을 출력하죠. 특이한 것은 greeting 함수가 끝나도, helloFunc 함수는 여전히 name을 기억한다는 것입니다.

함수가 끝나면 변수도 끝나는 것 아닌가?

함수의 지역 변수는 보통 함수 안에서만 유효합니다. 하지만 name이 살아있는 이유는 hello 함수가 클로저(closure)를 형성하기 때문입니다.

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

클로저를 이해하려면 렉시컬 환경을 알 필요가 있습니다.

렉시컬 환경

자바스크립트에서 실행 중인 함수, 코드 블록({...}), 스크립트 전체렉시컬 환경이라는 객체를 가집니다. 렉시컬 환경 객체는 두 가지로 이루어져 있습니다.

  • 환경 레코드: 모든 지역 변수를 프로퍼티로 저장하고 있는 객체. this와 같은 기타 정보도 여기에 저장.
  • 외부 렉시컬 환경에 대한 참조

단계1. 변수

환경 레코드의 프로퍼티입니다. 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경하는 것이죠.

위 두 줄 짜리 코드에는 렉시컬 환경이 하나만 존재합니다. 이를 전역 렉시컬 환경이라고 합니다.

  • 네모상자: 변수가 저장되는 환경 레코드
  • 화살표: 렉시컬 환경에 대한 참조

전역 렉시컬 환경은 외부 참조를 가지지 않아 화살표가 null을 가리킵니다. 좀 더 긴 코드로 전역 렉시컬 환경이 어떻게 변하는지 보겠습니다.

줄1: execution start

  • 스크립트 내 선언한 변수 전체가 렉시컬 환경에 올라갑니다.
  • greeting 변수는 'uninitialized'로 참조 불가능한 상태입니다.

줄2: let greeting

  • 값 할당 전이므로 'undefined'입니다. 이제 greeting 사용이 가능합니다.

줄3: greeting = 'hello'

  • 값 할당

줄4: greeting = 'bye'

  • 값 변경

단계2. 함수 선언문

함수 선언문은 변수와 다르게 바로 초기화됩니다.

그래서 함수 선언문은 선언 전 사용이 가능합니다.

let greeting = 'hello';

say('Jason');

function say(name) {
	alert(`${greeting}, ${name}!`);
}

단계3. 내부와 외부 렉시컬 환경

함수를 실행하면 새로운 렉시컬 환경이 만들어집니다. 함수 매개변수와 지역 변수가 이 환경에 올라가죠. say 함수에는 지역 변수가 없으니 매개변수인 name만 올라갑니다.

say 함수 실행 후 alert 호출 시점의 환경은 다음과 같습니다.

say 함수의 내부 렉시컬 환경이 외부의 전역 렉시컬 환경을 참조합니다.

코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡습니다. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장합니다. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복됩니다.

전역 렉시컬 환경에서도 변수를 찾지 못하면 어떻게 될까요?

  • 엄격 모드: 에러!
  • 비 엄격 모드: 새로운 전역 변수 생성!

say 함수에서 변수 찾기가 어떻게 진행되는지 알아봅시다.

  • name 변수는 내부 렉시컬 환경에서 찾았습니다.
  • greeting 변수는 내부 렉시컬 환경에 없어서 외부 렉시컬 환경으로 확장하여 찾았습니다.

단계4. 함수를 반환하는 함수

함수에는 중요한 사실 하나가 있습니다.

모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다

함수는 [[Environment]]라는 숨김 프로퍼티를 가지는데, 여기에 렉시컬 환경에 대한 참조가 있습니다. [[Environment]]는 딱 한 번 값이 정해지고 변하지 않습니다.

function makeSay() {
  const greeting = 'Hello';
  return function (name) { 
    console.log(`${greeting}, ${name}!`);
  };
}

let say = makeSay();
say('Jason');

makeSay가 반환하는 중첩함수의 [[Environment]]는 다음과 같습니다.

say함수를 실행하면 새로운 렉시컬 환경이 만들어지죠. 이 렉시컬 환경이 참조하는 외부 렉시컬 환경이 바로 [[Environment]]가 참조하는 환경입니다.

이제야 클로저에 대한 퍼즐이 어느정도 맞춰지네요. 이쯤에서 클로저에 대한 좀 더 쉬운 정의를 끝으로 마무리 짓겠습니다.

클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미합니다

profile
점점 더 좋아지고 있습니다

0개의 댓글