JavaScript : 클로저

Sean H.W. Lee
5 min readJun 7, 2021

--

Photo by Joan Gamell on Unsplash

오늘은 클로저에 대해 정리. 일단 클로저에 대해 이해하려면 몇가지 사항에 대해 함께 알고 있어야 한다.

렉시컬 환경

렉시컬 환경이란?

자바스크립트에서는 실행 중인 함수, 코드 블록 {...} , 스크립트 전체는 렉시컬 환경이라는 내부 숨김 연관 객체를 갖는다고 하는데, 내부 숨김 연관 객체...?

렉시컬 환경 객체는 두 부분으로 구성된다.

  1. 환경 레코드(Environment Record) : 모든 지역 변수를 프로퍼티로 가지고 있는 객체. this 값과 같은 기타 정보도 여기 저장된다고 한다.
  2. 외부 렉시컬 환경(Outer Lexical Environment) 에 대한 참조 — 외부와 연관됨

내부 숨김 연관 객체가 무슨 말인지 이해는 잘 안되지만, 실행 중인 함수, 코드 블록, 스크립트 전체는 렉시컬 환경이라는 객체를 갖는다는 것만 기억하고 일단 다음으로 넘어가보자.

변수

변수는 환경 레코드 라고 하는 객체의 프로퍼티 라고 한다. 따라서 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경하는 것을 의미한다.

예를들어 아래와 같이 변수가 하나 선언되고, 그 변수에 값이 할당되면 아래와 같은 환경들이 만들어진다.

  • 렉시컬 환경 : 스크립트 전체와 관련된 렉시컬 환경은 전역 렉시컬 환경이라고 한다.
  • 환경 레코드 : 여기에 hello: 'world' 가 저장된다
  • 외부 렉시컬 환경 : 아래 코드는 외부에 참고하고 있는 값이 없으므로 null 을 가리킨다.
let hello = 'world';

코드가 실행되면 전역 렉시컬 환경도 변화한다고 하는데, 일단 위 코드에서 변수가 선언되서 값이 할당되는 과정을 조금 더 자세히 풀어보면 아래와 같다.

  1. 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다.
  • 이때 변수의 상태는 특수 내부 상태인 ‘uninitialized’가 된다. 자바스크립트 엔진은 ‘uninitialized’ 상태의 변수를 인지하기는 하지만, let 을 만나기 전까지는 이 변수를 참조할 수 없다고 한다.

2. let hello 를 만나면, 값을 할당하기 전이기 때문에 프로퍼티 값이 undefined 이기는 하지만 이때부터 변수를 사용할 수 있다.

3. hello 에 값이 할당된다

❓다만, 렉시컬 환경은 ECMAScript에서 자바스크립트의 동작을 설명하기위한 “이론상의” 객체라고 한다. 따라서 엔진마다 명세서를 준수하면서 이 환경을 나름의 방법으로 최적화한다고 한다. 흠…❓

함수 선언문

자바스크립트에서 함수는 변수와 마찬가지로 값이다. 다만 함수 선언문으로 선언한 함수는 일반 변수와 달리 바로 초기화된다. (변수는 uninitilaized 상태가 먼저임) 때문에 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다.

단, 이는 함수 선언문으로 정의한 함수에만 적용된다. 함수를 변수에 할당한 함수 표현식은 해당하지 않는다.

내부와 외부 렉시컬 환경

함수를 호출해 실행하면 새로운 렉시컬 환경이 만들어지고, 이 렉시컬 환경에는 함수 호출 시 넘겨받은 매개변수와 함수의 지역 변수가 저장된다. 즉, 함수 호출중인 동안은 호출 중인 함수를 위한 내부 렉시컬 환경과 내부 렉시컬 환경이 가리키는 외부 렉시컬 환경 두개를 갖게 된다.

예를들어 아래와 같은 코드를 실행시키면,

  • lan 이라는 함수를 위한 내부 렉시컬 환경이 만들어지고, 내부 렉시컬 환경에는 name 이라는 인자를 통한 프로퍼티가 존재하게 된다.
  • 외부 렉시컬 환경에는 hello 라는 변수와 lan 이라는 함수를 프로퍼티로 갖게 된다.

내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다. 따라서 코드에서 변수에 접근할 때, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다. 내부 환경에서 원하는 변수를 찾지 못하면 내부 환경이 참조하는 외부 환경까지 검색 범위를 넓히게 된다. 이 과정은 검색 범위가 전역 렉시컬 환경이 될 때까지 계속된다.

이걸 아래 코드에 다시 적용해 보면

  • 함수 내부에서 변수 name 에 접근할 때는 내부 렉시컬 환경에서 찾고,
  • hello 에 접근하려는데 함수 내부에는 이에 해당하는 프로퍼티가 없기 때문에 검색 범위를 외부로 확장하게 되고, 외부 환경에서 hello 를 찾는다.
let hello = 'world';
function lan(name) {
console.log(`${hello}, ${name}`);
}

클로저

모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다. 함수는 [[Environment]] 라는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참고가 저장된다. ([[Environment]] 는 내부 슬롯과 연계된 내용 같은데, 일단 이 글의 논지에서 살짝 벗어나는 면도 있고, 사실 읽어봐도 잘 이해가 안 되서 별도 정리가 필요할 듯 하다. 일단은 [[Environment]] 에 함수가 만들어진 곳의 환경에 대한 참조가 저장된다고만 받아들여도 클로저를 이해하는 데에는 무리 없을듯 하다.)

[[Environment]] 가 뜬금없이 나온 이유는, 일단 클로저의 정의는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수 이다. 말인즉, 자바스크립트의 함수는 [[Environment]] 를 통해 자신이 어디서 만들어졌는지를 기억하고, 함수 내부 코드는 [[Environment]] 를 사용해 외부 변수에 접근한다. 따라서 함수가 자신의 내부에 찾는 변수가 없으면, [[Environment]] 를 통해 자신이 만들어진 환경을 기억해서 그곳으로 찾아가 자신이 필요한 변수를 찾는다. 때문에 자바스크립트의 함수는 모든 함수가 클로저이다.

--

--