함수와 함수가 선언된 어휘적 환경의 조합
클로저를 이해하기 위해서는 선행적으로 어휘적 범위 지정(Lexical scoping)에 대해 이해해야 한다.
어휘적 범위 지정(lexical scoping) 이란 스코프가 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다는 것을 말한다. 여기서 "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다.
즉, 렉시컬 환경의 상∙하위 관계는 함수의 선언 위치에 따라 정해진다.
자바스크립트에서 실행 중인 함수, 코드 블록 {...}, 스크립트 전체는 렉시컬 환경(Lexical Environment) 이라 불리는 내부 숨김 연관 객체(internal hidden associated object)를 갖으며, 렉시컬 환경 객체는 두 부분으로 구성된다.
환경 레코드(Environment Record): 모든 지역 변수를 프로퍼티로 저장하고 있는 객체로 this 값과 같은 기타 정보도 여기에 저장된다.
외부 렉시컬 환경(Outer Lexical Environment) 에 대한 참조: 외부 코드와 연관된다.
’변수’는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이며, 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경함을 의미한다.
함수에서의 렉시컬 환경
함수 선언문으로 선언한 함수는 일반 변수와는 달리 바로 초기화된다는 차이점이 있다. 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다.
내부와 외부 렉시컬 환경
함수를 호출해 실행하면, 새로운 렉시컬 환경이 자동으로 만들어지며 이 렉시컬 환경에는 함수 호출 시 넘겨받은 매개변수와 함수의 지역 변수가 저장된다. 객체나 중첩된 함수의 내부 함수의 경우도 마찬가지 이다.
코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장하는데 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복된다.
함수를 리턴하는 함수
함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는다. 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 그 프로퍼티에 저장되어 외부 렉시컬 환경의 검색을 가능하게 한다.
[[Environment]]는 함수가 호출 장소에 상관없이 자신의 선언 위치를 알 수 있게 하고, 함수가 생성될 때 한번만 값이 세팅되고, 변하지 않는다.
function makeCounter() {
let count = 0;
return function() { // 내부 렉시컬 환경
return count++;// [[Environment]] => count: 0 => |makeCounter function => null
// ▴ |counter : undefined
}; // 증가한 count값 저장
}
let counter = makeCounter();
counter.[[Environment]]
에는 {count: 0}
이 있는 렉시컬 환경에 대한 참조가 저장된다.counter.[[Environment]]
에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조한다.count++
가 실행되면서 count가 1 증가한다.자바스크립트에서 모든 객체는 도달 가능한 상태일 때만 메모리에 유지된다. 그리고 함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거된다. 그렇기 때문에 함수 호출이 끝나면 관련 변수를 참조 할 수 없게 된다.
바로 이러한 이유때문에 함수 호출이 끝난 후에도 렉시컬 환경이 메모리에 유지된다.
function f() {
let value = 123;
return function() {
alert(value);
}
}
let g = f(); // g가 살아있는 동안엔 연관 렉시컬 환경도 메모리에 존재
g = null; // 도달할 수 없는 상태가 되었으므로 메모리에서 삭제
클로저는 가비지 컬렉션에 의해 추척되어 삭제되지 않으므로 계속해서 메모리에 존재하게 된다. 그렇기 때문에 클로저의 남용은 퍼포먼스의 저하로 이어질 수 있다.
클로저는 함수와 함수가 선언된 어휘적 환경의 조합으로 정의하기도 하지만 기능적인 의미에서는 외부 변수를 기억하고 그 외부 변수에 접근할 수 있는 함수를 말하기도 한다. 몇몇 언어에서는 클로저를 구현하는게 불가능하거나 특수한 방식으로 함수를 작성해야하지만, 자바스크립트에서는 하나의 예외를 제외하고 모든 함수가 자연스럽게 클로저가 된다.
가장 대표적인 예는 중복함수에서 반환되는 내부 함수이다.
렉시컬 환경에서 설명한 이유들로 외부 변수를 기억하고 접근가능한 함수가 만들어 진다.
즉, 함수가 가지는 [Environment]
프로퍼티로 인해 우리가 아는 클로저의 기능이 작동한다.
오늘은 블로그에 글을 두 개 올리게 됐다. 다른 사람들의 글이나 큰 사이트들의 글을 읽어보면 아직도 내가 모르는 것이 많고, 글쓰는 실력도 부족하다는 생각이 들게 된다. 많이 노력해야겠다 라는 생각도 들면서 동시에 시간이 지나 나를 돌아봤을 때 스스로 뿌듯할정도로 지금보다 많이 성장해 있었으면 좋겠다.