fffo
클로저 본문
클로저
렉시컬 스코프
- js는 렉시컬 스코프를 따르는 프로그래밍 언어임
- 렉시컬 스코프는 상위 스코프 결정 방식
- js 에서는 변수를 현재의 렉시컬 스코프 내에서 찾지 못하면 상위 스코프로 이동하며 찾음
- 이 때 상위 스코프는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정됨
함수 객체의 내부 슬롯 [[Environment]]
- 함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경(위치), 즉 상위 스코프의 참조를 저장함
- 저장하는 시기는 함수 정의가 평가되는 시점에 저장
- 함수 정의가 평가되는 시점은 해당 코드가 평가되는 시점
- 코드가 평가 될 때 함수 객체가 생성되고, 해당 함수 객체의 내부 슬롯 [[Environment]]에 현재 실행되고 있는 콜스택의 렉시컬 환경의 참조를 넣음
- 추후에 함수가 실행되어 함수의 실행 컨텍스트를 생성하고 렉시컬 환경을 콜스택에 올릴 때 렉시컬 환경의 외부 렉시컬환경 참조로 해당 함수가 가지고 있던 내부 슬롯 [[Environment]]에 있던 참조 값을 넣음
클로저와 렉시컬 환경
- 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있음. 이러한 중첩 함수를 클로저(closure)라고 부름
- 모든 함수는 클로저이지만 일반적으로 클로저라 불리는 함수는 다음과 같은 특징을 가짐
- 외부 함수보다 더 오래 살아있음
- 외부 함수의 변수를 참조함
- 클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수(free variable)라고 부름
- 모던 js엔진은 클로저가 참조하고 있지 않는 식별자는 기억하지 않는 최적화를 해주므로 클로저로 인한 불필요한 메모리 낭비는 없음
클로저의 활용
- 상태(state)를 안전하게 변경하고 유지하기 위해 클로저를 사용함
- 상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태 변경을 허용
function makeCounter(predicate) {
let counter = 0;
return function () {
counter = predicate(counter);
return counter;
};
}
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
const increaser = makeCounter(increase);
console.log(increaser());
console.log(increaser());
const decreaser = makeCounter(decrease);
console.log(decreaser());
console.log(decreaser());
- 전역 평가
- 전역 객체 생성, 전역 실행 컨텍스트 생성, 전역 렉시컬 환경 생성 후 바인딩, 콜 스택에 추가
- 전역 환경 레코드 생성 및 전역 렉시컬 환경에 바인딩
- 전역 환경 레코드에 객체 환경 레코드와 선언적 환경 레코드 바인딩
- 객체 환경 레코드에 전역 객체 바인딩 후 전역 객체에 makeCounter, increase, decrease 함수 객체 생성 및 바인딩
- 함수 객체 생성 시 내부 슬롯 [[Environment]]에 현재 실행 컨텍스트인 전역 렉시컬 환경 바인딩
- 선언적 환경 레코드에 increaser, decreaser 변수를 프로퍼티로 선언
- 전역 실행
- increaser 변수에 makeCounter(increase) 함수 반환 값을 할당하기 위해 makeCounter 함수 실행
- 함수 평가 (makeCounter)
- makeCounter함수의 실행 컨텍스트 생성, 함수 렉시컬 환경 생성 후 바인딩, 콜 스택에 추가
- 함수 환경 레코드 생성 및 함수 렉시컬 환경에 바인딩 후 인자로 전달 받은 increase 함수 객체를 매개변수 predicate에 할당, counter 변수를 프로퍼티로 선언
- this바인딩은 makeCounter함수가 일반 함수로 호출되었으므로 전역 객체 바인딩
- 외부 렉시컬 환경 참조는 makeCounter함수 객체가 생성될 때 내부 슬롯 [[Environment]]에 바인딩 되었던 전역 렉시컬 환경 바인딩
- 함수 실행 (makeCounter)
- counter에 0을 할당
- 리턴 값으로 사용되는 함수 객체 생성 및 리턴
- 이 때 생성되는 함수 객체의 내부 슬롯 [[Environment]]는 현재 실행 컨텍스트인 makeCounter의 렉시컬 환경을 바인딩
- 함수 종료 후 콜 스택에서 pop
- 리턴된 함수 객체를 increaser에 바인딩
- (console을 찾는 스코프체인과 log를 찾는 프로토타입체인 과정 생략)
- increaser함수 실행
- 함수 평가
- increaser함수의 실행 컨텍스트 생성, 함수 렉시컬 환경 생성 후 바인딩, 콜 스택에 추가
- 함수 환경 레코드 생성 및 함수 렉시컬 환경에 바인딩
- this바인딩은 함수가 일반 함수로 호출되었으므로 전역 객체 바인딩
- 외부 렉시컬 환경 참조는 increaser함수 객체가 생성될 때 내부 슬롯 [[Environment]]에 바인딩 되었던 makeCounter의 렉시컬 환경 바인딩
- 함수 실행
- counter 변수가 현재 함수 환경 레코드에 없으므로 외부 렉시컬 환경 참조로 바인딩된 makeCounter의 렉시컬 환경으로 가서 찾기
- makeCounter의 렉시컬 환경의 환경 레코드에 있는 counter변수에 predicate함수의 반환값을 넣기 위해 predicate 함수를 동일한 방법으로 찾아서 실행
- ... 생략 ...
캡슐화와 정보 은닉
- 클로저를 활용해 타 언어의 public, private, protected 같은 접근 제한자 흉내를 낼 수 있음
const Person = (function () {
let _age = 0;
function Person(name, age) {
this.name = name;
_age = age;
}
Person.prototype.sayHi = function() {
console.log(`Hi! I'm ${this.name}, ${_age} years old`);
};
return Person
}());
const p1 = new Person('dlwlrma', 29);
const p2 = new Person('oilnam', 81);
p1.sayHi(); // Hi! I'm dlwlrma, 81 years old
p2.sayHi(); // Hi! I'm oilnam, 81 years old
- 그러나 위와 같이 인스턴스 메서드 대신 프로토타입 메서드를 사용하면 프로토타입 메서드는 한 번만 정의되기 때문에 클로저의 렉시컬 환경도 하나가 됨
- private 타입은 후에 배울 새로운 표준 사양으로 구현됨
'Programming > Javascript' 카테고리의 다른 글
클래스 상속 (0) | 2021.10.15 |
---|---|
클래스 (0) | 2021.10.13 |
실행 컨텍스트 (0) | 2021.10.07 |
this (0) | 2021.10.06 |
빌트인 객체 (0) | 2021.10.05 |
Comments