❓클로저란 "클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다." => 무슨 말인지는 찬찬히 알아보자
-
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다.➡️ 렉시컬 스코프(정적 스코프)
const x = 1; function foo() { const x = 10; bar(); } function bar() { console.log(x); } foo(); bar();
➡️
- foo()와 bar() 모두 전역에서 정의된 전역 함수로 상위 스코프는 전역이다.
- 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않는다.
- "함수의 상위 스코프를 결정하는 것" == "렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정하는 것"
- 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.
- 함수가 정의된 환경(위치)와 호출되는 환경(위치)는 다를 수 있다. 렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과는 상관없이 자신이 정의된 환경(상위 스코프)를 기억해야한다. → 함수는 자신의 내부 슬롯에 자신이 정의된 환경(상위 스코프의 참조)를 저장한다. → 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다.
- 함수 객체의 내부 슬롯에 저장된 현재 실행중인 실행 컨텍스트의 렉시컬 환경의 참조 == 상위 스코프 자신이 호출되었을 때 생성될 함수 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장될 참조값 함수 객체는 내부 슬롯 [[Environment]]에 저장한 렉시컬 환경의 참조(상위 스코프)를 자신이 존재할 때까지는 기억한다.
const x = 1;
function foo() {
const x = 10;
// 상위 스코프는 "함수 정의 환경"에 따라 결정된다.
// 함수 호출 위치와 상위 스코프는 아무 관계가 없다.
bar();
}
// 함수 bar는 자신의 상위 스코프, 즉 전역 렉시컬 환경을 [[Environment]]에 저장하여 기억한다.
function bar() {
console.log(x);
}
foo();
bar();➡️
- foo()와 bar() 함수는 모두 전역 코드가 평가되는 시점에 함수 객체를 생성하고 전역 객체 window의 메서드가 된다. → 전역 코드 평가 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경인 전역 렉시컬 환경의 참조가 저장된다.
- 함수 호출 시 함수 내부로 코드의 제어권이 이동한다.
- 함수 코드를 평가한다.
[함수 코드 평가 순서]
- 함수 실행 컨텍스트 생성
- 함수 렉시컬 환경 생성 2.1. 함수 환경 레코드 생성 2.2. this 바인딩 2.3. 외부 렉시컬 환경에 대한 참조 결정
- 외부 렉시컬 환경에 대한 참조는 함수 객체의 내부 슬롯에 저장된 렉시컬 환경의 참조가 할당된다.
const x = 1;
// ①
function outer() {
const x = 10;
const inner = function () {
console.log(x);
}; // ②
return inner;
}
// outer 함수 호출 시, 중첩 함수 inner는 반환한다.
// outer 함수의 실행 컨텍스트는 실팽 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10-
outer() 호출 시 중첩 함수 inner()를 반환하고 생명주기를 마감한다. outer() 실행 종료 시 outer 함수의 실행 컨텍스트는 스택에서 제거된다. 그러나, ④ 실행 결과는 outer 함수의 지역 변수 x의 값인 10이다.
-
외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료된 외부 함수의 변수를 참조 가능하다. → 클로저라고 지칭한다
즉, 정의에서 본 "그 함수가 선언된 렉시컬 환경"은 상위 스코프를 의미하는 실행 컨텍스트의 렉시컬 환경을 뜻한다.
-
inner 함수는 선언될 당시 자신을 감싼 outer의 렉시컬 환경을 기억함. 즉, inner 함수는 자신이 어디서 호출될지와 상관없이, outer 함수의 스코프를 기억하는 클로저가 되는 것.
- outer()는 inner를 반환함.
- 이때 outer 함수의 실행이 끝나면 x = 10을 포함한 실행 컨텍스트는 일반적으로는 사라져야 하지만… 💡 inner 함수가 반환되면서 외부 변수에 의해 참조되고 있으므로, outer의 변수 환경은 **GC(가비지 컬렉터)**에 의해 수거되지 않고 heap 메모리에 유지됨. 이게 바로 클로저의 핵심!
📦 Call Stack -------------------------- | innerFunc() 호출 | | outer() 호출 (끝남) | -------------------------- 🧠 Heap Memory -------------------------- | innerFunc (function) | ---> 📎 [[Environment]]: outer's scope | outer() 환경 참조 보존 | └─ x: 10 -------------------------- -
클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수라고 지칭한다.
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다. 즉, 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
💡 상태 유지
const createCounter() {
// 카운트 상태 변수
let count = 0; // 외부 함수의 변수 (상태)
// 클로저인 메서드를 갖는 객체를 반환하다.
// 객체 리터럴은 스코프를 만들지 않는다.
// 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다.
return function() {
count++; // 클로저로 외부 함수의 변수인 count에 접근
console.log(`현재 카운트: ${count}`);
};
}
const counter = createCounter();
counter(); // "현재 카운트: 1"
counter(); // "현재 카운트: 2"
counter(); // "현재 카운트: 3"❓ 캡슐화란
- 객체의 생태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용
- 객체 간의 상호 의존성(결합도)를 낮추는 효과가 있음
- 객체지향 프로그래밍 언어는 클래스 정의
클래스를 구성하는 멤버(프로퍼티와 메서드)에 대하여
public, private, protected같은 접근 제한자를 선언하여 공개 범위를 한정함 public으로 선언된 프로퍼티와 메서드는 클래스 외부에서 참조할 수 있지만,private으로 선언된 경우는 클래스 외부에서 참조 불가능
→ JS는 접근 제한자를 제공하지 않고 객체의 모든 프로퍼티와 메서드는 외부에 공개됨. 즉, 기본적으로 public이다.
