💡 클로저
클로저란?
- 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
렉시컬 스코프(정적 스코프)
- 자바스크립트 엔진은 함수를 어디에 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프) 라 한다.
- 상위 스코프에 대한 참조는 함수 정의가 평가 or실행되는 시점(함수 객체 생성)에 함수가 정의된 위치에 의해 결정된다.
- 함수 선언문 → 호이스팅 때 결정(평가)
- 함수 표현식 → 실행되어 함수 객체 생성될 때 (실행)
- 결정 시기만 다를 뿐 상위 스코프는 정의된 위치에 따라 결정된다.
- 스코프
- 스코프의 실체는 실행컨텍스트의 렉시컬 환경이다
- 스코프 체인
- 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결된다.
함수 객체의 내부 슬롯 [[Environment]]
- 함수는 자신의 내부 슬롯[[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다
- 함수가 정의된 위치와 호출되는 위치는 다를 수 있기 때문에 렉시컬 스코프가 가능하려면 정의된 환경을 기억해야 한다
- 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다.
- (중요) 함수 객체가 생성될 때 [[Environment]] 슬롯에 상위 스코프 참조를 저장하기 때문에 어디서 호출해도 상위 스코프의 식별자를 참조할 수 있다.
- 함수가 호출 과정
- 함수가 호출되면 함수 내부로 코드의 제어권이 이동
- 함수 코드 평가 시작 (실행준비)
- 함수 실행 컨텍스트 생성
- 함수 렉시컬 환경 생성
- 함수 환경 레코드 생성
- this 바인딩
- 외부 렉시컬 환경에 대한 참조 결정 (선언문)
클로저와 렉시컬 환경
- 클로저
- 외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 **클로저(closer)**라고 부른다.
- 함수가 자유 변수에 대해 닫혀있다는 의미이다.
- 자유변수
- 클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수라고 부른다.
- 자유변수
- 함수 객체가 다른 함수 객체의 [[Environment]] 내부 슬롯에 참조되고 있으면 실행 컨텍스트 스택에 제거되어도 소멸되지 않는다.
- 가비지 컬렉터는 누군가가 참조하고 있는 메모리 공간을 함부로 해제하지 않는다.
- 클로저라고 하지 않는 경우
- 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저다. 하지만 일반적으로 모든 함수를 클로저라고 하지 않는다.
- 중첩 함수가 외부 함수의 어떤 식별자도 참조하지 않는 경우
- 중첩 함수가 외부 함수보다 생명주기가 짧아 소멸되는 경우
클로저의 활용
- 정보 은닉
- 클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다
- 즉, 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용
- 보통 즉시 실행 함수를 호출하여 외부 함수 실행컨텍스트를 생성하고 클로저를 생성하나 봄
- 한 번만 실행되므로 자유변수가 재차 초기화될 일이 없고 외부에서 직접 접근할 수 없는 은닉된 private 변수가 되기 때문이다.
캡슐화와 정보 은닉
- 캡슐화
- 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다.
- 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라고 한다.
- 의도치 않는 객체의 상태가 변경되는 것을 방지해 정보를 보호하고, 객체 간의 상호 의존성, 즉 결합도를 낮추는 효과가 있다.
- 식별자 접근 제한 범위
- 대부분의 객체지향 프로그래밍 언어는 멤버에 접근제한자를 선언하여 공개 범위를 한정할 수 있다.
- 자바스크립트는 접근 제한자가 없어 기본적으로 public 하다
// 생성자 함수
const Person = (function() {
let _age = 0; // private
// 생성자 함수
function Person(name,age) {
this.name = name; //public
_age = age;
}
//프로토타입 메서드
Person.prototype.sayHi = function(){
console.log(`${this.name}. I am ${_age}`)
}
// 생성자 함수를 반환
return Person
}());
const me = new Person('Oh',100)
const you = new Person('Kim',200)
- Person 생성자 함수와 sayHi 메서드는 이미 종료되어 소멸한 즉시 실행 함수의 지역 변수 _age를 참조할 수 있는 클로저다.
- 하지만 위 코드는 Person 생성자 함수로 만든 인스턴스끼리 자유 변수를 공유하게 되는 문제가 발생한다.
- you가 _age 바꾸면 me의 _age도 you가 바꾼 걸로 변경됨
- 이처럼 자바스크립트는 정보 은닉을 완전하게 지원하지 않는다
자주 발생하는 실수
블록레벨 스코프 내부의 var 키워드
- for 문의 변수 선언문에서 var 키워드로 선언한 i 변수는 블록 레벨 스코프가 아닌 함수 레벨 스코프를 갖기 때문에 전역 변수이다
var funcs = [];
for (var i=0; i<3; i++){
funcs[i] = function() {return i;}
}
for (var j=0; j<funcs.length;j++){
console.log(funcs[j]())
}
// 3 출력
- for 문의 변수 선언문에서 let 키워드로 선언한 변수를 사용하면 for문의 코드 블록이 반복 실행될 때마다 for문 코드 블록의 새로운 렉시컬 환경이 생성된다.
const funcs = [];
for (let i=0; i<3; i++){
funcs[i] = function() {return i;}
}
for (var j=0; j<funcs.length;j++){
console.log(funcs[j]()) // 0 1 2
}
“평가(실행준비)” ⇒ “실행” 의 반복
- 평가 : 실행하기 위해 식별자 등록 (호이스팅)
- 함수 객체 생성 시 상위 스코프 저장
- 함수 호출 시 해당 함수 스코프 “평가(실행준비)” ⇒ “실행
- 블록 스코피 실행 시 “평가(실행준비)” ⇒ 실행
반응형
'📝 Javascript > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
[Javascript] 모던 자바스크립트 Deep Dive - 38장 브라우저의 렌더링 과정 (0) | 2024.07.30 |
---|---|
[Javascript] 모던 자바스크립트 Deep Dive - 26장 ES6 함수의 추가 기능 (0) | 2024.07.25 |
[Javascript] 모던 자바스크립트 Deep Dive - 22, 23장 this, 실행컨텍스트 (1) | 2024.07.17 |
[Javascript] 모던 자바스크립트 Deep Dive - 20장 strict mode (0) | 2024.01.08 |
[Javascript] 모던 자바스크립트 Deep Dive - 18장 함수와 일급 객체 (1) | 2024.01.06 |