본 포스팅은 책 <모던 자바스크립트 입문>의 내용을 바탕으로 작성되었으며, 내용 보충이 필요한 부분들은 참고자료에 있는 문서들을 참고해서 작성하였음을 알려드립니다.
자바스크립트 엔진은 실행 가능한 코드를 만나면 그 코드를 평가해서 실행 문맥으로 만든다.
실행 가능한 코드는 다음과 같다.
💡 실행 문맥이란?
실행 가능한 코드가 실제로 실행되고 관리되는 영역. 실행에 필요한 모든 정보를 컴포넌트 여러 개가 나누어 관리하도록 만들어져 있음.
✔️ 컴포넌트의 종류
🔶 렉시컬 환경(LexicalEnvironment) 컴포넌트
자바스크립트 코드를 실행하기 위한 자원을 모아 둔 곳. 함수 또는 블록의 유효 범위 안에 있는 식별자와 그 결괏값이 저장되는 곳.
🤔 식별자(identifier)란? 변수/함수의 이름을 뜻함.
렉시컬 환경 컴포넌트의 구성요소
🔶 디스 바인딩(This Binding) 컴포넌트
그 함수를 호출한 객체의 참조가 저장되는 곳. 이것이 가리키는 값이 곧 해당 실행 문맥의 this가 됨.
❗ 컴포넌트의 종류에는 변수 환경(VariableEnvironment) 컴포넌트라는 컴포넌트가 더 있다. 하지만 <모던 자바스크립트 입문>에서 다음과 같은 이유로 렉시컬 환경 컴포넌트로 통일해서 설명하고 있기 때문에 본문에서도 구분하지 않았다.
렉시컬 환경 컴포넌트와 변수 환경 컴포넌트는 앞으로 설명할 렉시컬 환경 타입의 컴포넌트입니다. 두 컴포넌트는 타입이 같고 실제로 with 문을 사용할 때를 제외하면 내부 값이 같으므로 똑같이 취급해도 무리가 없습니다.
🤔 환경 레코드의 선언적 환경 레코드 vs 객체 환경 레코드
a
, b
, c
는 모두 선언적 환경 레코드의 바인딩이다.function foo(a) {
var b = 10;
function c() {}
}
아래 코드의 catch 문에서, e
가 선언적 환경 레코드의 바인딩이 된다. try {
...
} catch (e) {
...
}
자바스크립트 인터프리터는 시작하자마자
자바스크립트 엔진은 전역 코드를 평가할 때
프로그램이 평가된 다음에는 프로그램이 실행되며, 프로그램은 실행 문맥 안에서 실행된다.
자바스크립트는 식별자를 결정할 때 좀 더 안쪽 코드에 선언된 변수를 사용한다.
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
- 클로저는 함수가 생성될 때 같이 생성된다.
렉시컬 스코핑이란 함수가 중첩된 상황에서 파서가 변수 이름을 결정하는 스코핑 방식으로, 이 스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다.
다음의 예시에서,
function init() {
var name = 'Mozilla'; // name is a local variable created by init
function displayName() {
// displayName() is the inner function, a closure
console.log(name); // use variable declared in the parent function
}
displayName();
}
init();
displayName
함수 내부에는 name
변수가 없다.displayName
함수를 둘러싸고 있는 외부 함수 init
에 name
이 정의되어 있다.displayName
함수 내부의 변수 name
의 값을 결정할 때 우선 해당 함수 내부에 정의되어 있는지 탐색하고, 없을 경우 외부 렉시컬 환경 참조를 따라가면서 결정된다.name
은 init
함수 내부에서만 접근 가능하며, 내부에 중첩된 함수(displayName
)에서도 접근 가능하다.클로저를 잘 활용하면 현재 상태를 기억하고 변경된 최신 상태를 유지할 수 있다.
예를 들어, 카운터를 구현한다고 생각해보자.
특정 버튼을 클릭할 때 카운터 값이 하나씩 증가하게 하고 싶다면, 어딘가에서 현재 카운터 값을 기억하고 있어야 한다. 전역변수를 사용하는 방법도 있겠지만, 의도치 않게 값이 변경될 수 있다는 위험이 있다. 이럴 때 다음과 같이 클로저를 사용하여 counter 변수를 기억할 수 있다.
const increase = (() => {
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 클로저를 반환
return function () {
return ++counter;
};
}()
);
변수 뿐만 아니라 함수 또한 클로저를 활용해서 private하게 만들 수 있다.
클로저는 ‘캡슐화된 객체’라고 할 수 있다.
다음과 같은 코드에서 출력값은 어떻게 될까?
function makeCounter(){
var count = 0;
return function() {
return count++;
}
}
var counter1 = makeCounter();
var counter2 = makeCounter();
console.log(counter1());
console.log(counter2());
console.log(counter1());
정답은 0, 0, 1이다. 그 이유는 makeCounter()
를 호출할 때마다 makeCounter
의 렉시컬 환경 컴포넌트가 새로 생성되기 때문이다. 따라서 각 클로저는 서로 다른 내부 상태를 저장한다.
참고 자료
<모던 자바스크립트 입문> 이소 히로시 지음
[JS]Execution Context와 Call Stack - 어? 쓰흡... 하아.... | Dev X
ECMA-262-5 in detail. Chapter 3.2. Lexical environments: ECMAScript implementation.
Closures - JavaScript | MDN
Closure | PoiemaWeb