자바스크립트 클로저

배기호 Notebook·2023년 7월 21일
0

JavaScript

목록 보기
8/13

자바스크립트 클로저

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.
클로저를 이해하기 위해 먼저 자바스크립트가 어떻게 변수의 유효범위를 지정하는 지 (Lexical scoping)를 이해해야 한다.

Lexical Environment

변수

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

렉시컬 환경 객첸은 두 부분으로 구성되는데

  • 환경 레코드 : 모든 지역 변수를 프로퍼티로 저장하고 있는 객체
  • 외부 렉시컬 환경에 대한 참조 : 외부 코드와 연관됨

    '변수'는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이다.
    '변수를 가져오거나 변경'하는 것은 '환경 레코드의 프로퍼티를 가져오거나 변경'함을 의미한다.
let phrase = "Hello";
alert(phrase);

위 코드에는 하나의 렉시컬 환경이 존재한다.

이처럼 스크립트 전체와 관련된 렉시컬 환경을 전역 렉시컬 환경이라고 한다.
전역 렉시컬 환경은 외부 참조를 갖지 않는다.


전역 렉시컬 환경 변화

let phrase;
phrase = "Hello";
phrase = "Bye";
  1. 스크립트가 시작되면서 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다.
  • 이때 변수의 상태는 특수 내부 상태인 'uninitialized'이다.
    자바스크립트 엔진은 uninitialized 상태의 변수를 인식하지만, let을 만나기 전 까지 이 변수를 참조할 수 없다.
  1. let phrase를 발견하고 변수의 공간을 확보, phrase: undefined.
  2. phrase에 값을 할당
  3. phrase의 값 변경

변수는 특수 내부 객체인 환경 레코드의 프로퍼티이다.
환경 레코드는 현재 tlwfgod 중인 함수와 코드 블록, 스크립트와 연관되어 있다.
변수를 변경하면 환경 레코드의 프로퍼티가 변경된다.

함수 선언문

함수 또한 변수와 마찬가지로 값이다.

함수 추가 시의 전역 렉시컬 환경

let phrase = "Hello";

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

함수 선언문으로 선언한 함수는 일반 변수와 달리 바로 초기화된다.
즉, 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용이 가능하다. (변수의 경우 let을 만나 선언이 될 때까지 사용 할 수 없다.)

이는 함수 선언문으로 정의한 함수에만 적용된다.
let say = function(name)... 과 같은 함수 표현식의 경우 해당 x

내부, 외부 렉시컬 환경

let phrase = "Hello";

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

say("John");	// hello, John

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

함수가 호출 중인 동안 호출 중인 함수를 위한 내부 렉시컬 환경과,
내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 갖게된다.

내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다.

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

  • 함수 say의 내부의 alert에서 변수 name에 접근할 때, 먼저 내부 렉시컬 환경의 name을 찾는다.
  • alert에서 변수 phrase에 접근하려는 경우, 내부 렉시컬 환경에서 찾을 수 없다. 이때 외부 렉시컬 환경으로 검색 범위가 확장된다.

함수를 반환하는 함수

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어지고 여기에 makeCounter를 실행하는데 필요한 변수들이 저장된다.

위의 say("John") 예시와의 차이는
makeCounter()가 실행되는 도중에 (return count++)이 한줄 짜리인 중첩 g마수가 a나들어진다는 점이다.
현재 중첩함수가 생성되기만 하고 실행은 되지 않는 상태

모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다.
함수는 [[Environment]]라 불리는 숨김 프로퍼티를 통해, 함수가 만들어진 곳의 렉시컬 환경에 대한 참조를 저장한다.

따라서 counter.[[Environment]]엔 {count: 0}이 있는 렉시컬 환경에 대한 참조가 저장된다.
호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 건 바로 이 [[Environment]] 프로퍼티 때문이다.
[[Environment]]는 함수가 생성될 때 딱 한 번 값이 세팅되고 영원히 변하지 않는다.

counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성된다. 그리고 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉싴러 환경을 외부 렉시컬 환경으로 참조한다.

실행 흐름이 중첩 함수의 본문으로 넘어오면 count 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾습니다. 익명 중첩 함수엔 지역변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황입니다(). 이제 counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾아봅시다. count를 찾았습니다!

이제 count++가 실행되면서 count 값이 1 증가해야하는데, 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄집니다.

즉, 실행 종료 후 count: 1

counter()를 여러번 호출하면 count 변수가 2, 3 으로 증가하는 이유가 바로 이것이다.'

클로저

클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다. 자바스크립트에서는 모든 함수가 클로저가 된다.

참고
모던 JavaScript 튜토리얼

0개의 댓글