클로저를 이해하기 위해 호랑이굴에 들어가자 🐯

제이밍·2021년 10월 13일
9
post-thumbnail

준비운동

자바스크립트는 함수 지향 언어 입니다. 그렇기 때문에 함수를 동적으로 생성, 생성한 함수를 다른 함수로 인자로 넘길 수 있으며, 생성한 곳이 아닌 곳에서 함수를 호출 할 수 있습니다.

1. 변수의 유효범위를 이해한건 맞고?

코드블록

{...}
코드블록 안에서 선언한 변수는 블록 안에서만 사용 가능합니다.

{
  let message = "안녕하세요."; // 블록 내에서만 변숫값을 얻을 수 있습니다.
  alert(message); // 안녕하세요.
}

alert(message); // ReferenceError: message is not defined

블록이 없다면 이미 선언된 변수와 동일한 이름을 가진 변수 선언시 에러 발생!

중첩함수

함수 내부에서 선언한 함수를 중첩함수 라고 부릅니다.

function makeCounter() {
 let count = 0;

	// 중첩함수
 return function() {
   return count++;
 };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

🧐 위 함수를 보다보면 드는 의문이 있습니다.

만약 counter()을 여러개 선언하였을때 이 함수들은 서로 독립적인지?
함수와 중첩함수 내 count 변수엔 어떤 값이 할당되는지?

이런 의문을 해결하기 위해서 우리는 렉시컬 환경을 알 필요가 있습니다.

2.렉시컬환경

호랑이굴이라고도 불리는 렉시컬환경굴로 들어가 봅시다 🥲

변수

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

렉시컬 환경변수는 크게 두 부분으로 구성됩니다.
1. 환경레코드 : 모든 지역변수를 프러퍼티로 저장하고 있는 객체로, this 값이 여기에 저장된다.
2. 외부 렉시컬 환경 : 외부 코드와 연관되어 있는 것이 저장 된다.

변수 라는 말은 특수 내부 객체인 환경 레코드의 프로퍼티 중 하나일뿐,
변수를 가져오고 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경 함을 의미합니다.

📌 렉시컬 환경의 예시

렉시컬 환경의 생성 과 상태변화

  1. 스크립트가 시작되면 스크립트 내 선언한 변수 전체는 렉시컬 환경에 올라갑니다. (uninitialized)
  2. let phrase를 만난 이후 프로퍼티 값은 undefined로 바뀝니다. (uninitialized -> undefined)
  3. phrase 값이 할당 됩니다. (undefined -> 'Hello')
  4. phrase 값이 변경 되었네요. (Hello ->' 'Bye')

환경레코드는 실행중인 함수와 코드블록, 스크립트와 연관되어 있습니다.
변수를 변경하면 환경 레코드의 프로퍼티가 변경 됩니다.

🚧 코드를 사용해 직접 렉시컬 환경을 얻거나 조작하는 것은 불가능합니다.

함수 선언문

함수도 변수와 같이 값입니다.
다만 함수 선언문은 일반 변수와 다르게 렉시컬환경에서 바로 초기화 된다는 점에서 차이가 있습니다.

함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용가능합니다.
(변수는 let을 만나 선언되기 전까지 사용 할 수 없다)

선언되기전 함수를 사용할 수 있다는 말이 바로 이것 때문이지요!

🚧 단, let say = function(name)...같이 함수를 변수에 할당한 함수 표현식(function expression)은 해당하지 않습니다.

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

함수를 호출해 실행하게 되면 새로운 렉시컬 환경이 자동으로 만들어지게 되는데, 이 렉시컬 환경엔 함수 호출시 넘겨받은 매개변수와 함수의 지역변수가 저장됩니다.

예를들어 say("john")을 호출하면

위 예시와 같이 내부 렉시컬 환경과 외부 렉시컬 환경 동시에 갖게 됩니다.

  • 내부 렉시컬 환경에는 현재 실행중인 함수 say에 상응하게 되며,
  • 외부 렉시컬 환경에서는 phrase함수 say 두가지를 프로퍼티로 갖습니다.

코드가 변수를 찾는 과정

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

때문에 우리는 ${phrase} 와 ${name} 변수를 찾을 수 있게 되는 것 💥

🚧 전역 렉시컬 환경에 도달할 때까지 변수를 찾지 못하면 엄격 모드에선 에러가 발생합니다.

4.함수를 반환하는 함수

다시 돌아와 중첩함수에서 가진 의문을 해결해 봅시다.

함수가 호출될때 렉시컬환경이 생성

say("john") 예시처럼 함수 makeCounter()를 호출하면 2개의 새로운 렉시컬 환경이 만들어집니다.

  1. 내부 렉시컬 환경에는 함수블록의 count가 저장
  2. 외부 렉시컬 환경에는 function 과 counter 모두 저장 되게 됩니다.

동물은 가죽을 남기고, 함수는 [[Environment]]에 태어난 곳을 기록한다.

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

함수가 생성될때 [[Environment]]라 불리는 숨긴 프로퍼티를 갖게 되고, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장 되는 것이죠.

따라서 counter.[[Environment]]엔 { count:0 }이 있는 렉시컬환경에 대한 참조가 저장됩니다.
호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 건 바로 [[Environment]] 덕분이라 할 수 있습니다

[[Environment]]는 한번 값이 세팅되고 나면 영원이 변하지 않는 특징을 갖습니다.

렉시컬 환경이 있기에 클로저도 있을 수 있던것

외부에서 호출된 counter()은 자체 렉시컬 환경 변수에서 변수를 찾고, 없을 경우 외부렉시컬 환경을 참조하게 되는데 위 중첩함수엔 지역변수가 없이 때문에 렉시컬환경은 <empty> 상태이며, counter()의 외부 렉시컬 환경을 참조 하게 되며 count를 찾을 수 있는 것입니다!

이때 count 변수를 외부에서 참조하여 변경된 변수를 저장된 렉시컬 환경에서 이뤄지기 때문에
counter()을 여러번 호출했을때 count 변수가 2,3 으로 증가 할 수 있게 된것입니다.

따라서 클로저란..

클로저(closure)는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미합니다.
자바스크립트의 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억합니다. 함수 본문에선 [[Environment]]를 사용해 외부 변수에 접근합니다.

reference

https://ko.javascript.info/closure

profile
모르는것은 그때그때 기록하기

0개의 댓글