지난 포스팅에 이어서 오늘은 클로저(Closure) 에 대해 알아보고자 합니다.
먼저 클로저에 대한 이해를 위해서는 스코프(Scope)에 대한 선행지식이 있으면 좋으니, 지난 포스팅 혹은 스코프(가능하다면 실행컨텍스트까지) 다른 글들을 참고한 뒤에 해당 포스팅을 읽어주면 이해주시면 클로저에 대한 개념을 이해하는데 더 유용할 것이라고 생각합니다.
[Javascript] - 자바스크립트 JavaScript : 스코프(Scope)
자바스크립트 JavaScript : 스코프(Scope)
오늘은 지난 호이스팅에 이어 자바스크립트의 스코프에 대해서 알아보려고 합니다. 스코프(Scope) 1. 스코프(scope)란? 자바스크립트 스코프는 변수가 어디에서 접근 가능한지를 결정하는 개념입니
zzgh06.tistory.com
클로저(Closure)
먼저 클로저란?
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
글만 보았을 때는 이해하기가 다소 어려울 수 있는데요. 위의 정의에서 중요한 키워드는 " 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)" 입니다.
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
innerFunc();
}
outerFunc(); // 10
위의 코드를 살펴보면 함수 outerFunc 내에서 내부함수 innerFunc가 선언되고 호출되었습니다.
이때 내부함수는 외부함수의 변수 x에 접근할 수 있는데요. 그 이유는 내부함수가 외부함수 안에서 선언되었기 때문입니다.
더 자세히 설명 드리자면, 자바스크립트에서 함수의 상위 스코프는 함수가 호출된 시점이 아닌 선언된 위치에서 상위 스코프를 결정하게 됩니다. 이를 렉시컬 스코프 혹은 정적 스코프라고 하는데요.
정적 스코프 = 렉시컬 스코프란?
- 함수의 스코프를 결정하는 방식 중 렉시컬 스코프는 함수를 선언한 시점에 스코프를 결정하는 방식을 의미합니다.
- 함수가 중첩되어 있을 때, 내부 함수 내에 해당 변수가 존재하지 않을 경우 상위 스코프에서 해당 변수를 찾는 방식을 의미합니다.
위의 예제 처럼 내부함수 innerFunc가 함수 outerFunc 내에서 선언되었기 때문에 함수 innerFunc 상위 스코프로 outerFunc 로 결정된 겁니다.
혹은 함수 innerFunc가 전역에 선언되었다면 함수 innerFunc의 상위 스코프는 전역 스코프가 됩니다.
함수 innerFunc는 자신이 속한 렉시컬 스코프(전역, 함수 outerFunc, 자신의 스코프)를 참조할 수 있습니다.
아를 실행 컨텍스트 관점에서 설명해보자면
- 내부함수 innerFunc가 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고 변수 객체(Variable Object)와 스코프 체인(Scope chain) 그리고 this에 바인딩할 객체가 결정됩니다.
- 이때 스코프 체인은 전역 스코프를 가리키는 전역 객체와 함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체(Activation object) 그리고 함수 자신의 스코프를 가리키는 활성 객체를 순차적으로 바인딩합니다.
내부함수가 자신을 포함하고 있는 외부함수의 변수 x에 접근할 수 있는 것,
자바스크립트 엔진이 스코프 체인을 통해 인해 하위 스코프부터 상위 스코프(하위에서 상위의 단방향만 가능) 접근하여 원하는 변수를 검색해나가며 상위 스코프에 해당 변수가 존재하면 그 결과를 반환하고 그게 아니라면 에러를 출력하게 됩니다.
이번에는 내부함수 innerFunc를 함수 outerFunc 내에서 호출하는 것이 아니라 반환하도록 변경해봅니다.
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
return innerFunc;
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc();
inner(); // 10
함수 outerFunc는 내부함수 innerFunc를 반환하고 생을 마감했습니다.
즉, 함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법은 달리 없어 보이지만 위 코드의 실행 결과는 변수 x의 값인 10입니다.
이미 life-cycle이 종료되어 실행 컨텍스트 스택에서 제거되었지만 외부함수의 지역 변수 x의 값을 참조할 수 있는 이유는 함수가 속한 렉시컬 스코프를 기억하고 있기 때문에 함수가 렉시컬 스코프 밖에서 실행될 때도 이 스코프에 접근할 수 있게 해줍니다,
이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 함수를 클로저(Closure)라고 부릅니다.
이를 쉽게 정리하면
- 클로저란, 자신이 선언된 당시의 환경을 기억하는 함수입니다.
- 클로저란, 생명 주기가 끝난 외부 함수의 변수에 접근할 수 있는 내부 함수입니다.
더 쉽게 이해하기
- 클로저 = 함수 + 렉시컬 스코프
- 자바스크립트의 모든 함수는 자신이 선언된 환경의 주소를 저장하고 있습니다. 즉, 상위 스코프의 주소를 가지고 있는 것이죠.
- 함수 본문에서 상위 스코프의 주소를 참조하여 외부 함수의 변수에도 접근할 수 있게 됩니다.
클로저의 활용
그럼 이러한 클로저를 사용하는 이유는 뭘까요?
1. 상태유지
클로저는 현재 상태를 기억하고 변경된 최신 상태를 유지할 수 있습니다.
var box = document.querySelector('.box');
var toggleBtn = document.querySelector('.toggle');
var toggle = (function () {
var isShow = false;
// ① 클로저를 반환
return function () {
box.style.display = isShow ? 'block' : 'none';
// ③ 상태 변경
isShow = !isShow;
};
})();
// ② 이벤트 프로퍼티에 클로저를 할당
toggleBtn.onclick = toggle;
① 즉시실행함수가 반환한 함수는 자신이 생성됐을 때의 렉시컬 환경(Lexical environment)에 속한 변수 isShow를 기억하는 클로저 입니다.
② 클로저를 이벤트 핸들러로서 이벤트 프로퍼티에 할당하여 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않습니다.
③ 버튼을 클릭하면 이벤트 프로퍼티에 할당한 이벤트 핸들러인 클로저가 호출됩니다,
이때 .box 요소의 표시 상태를 나타내는 변수 isShow의 값이 변경된다. 변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하며 자신의 변경된 최신 상태를 게속해서 유지합니다.
이처럼 클로저는 현재 상태를 기억하고 이 상태가 변경되어도 최신 상태를 유지해야 하는 상황에 매우 유용하다.
2. 정보의 은닉
function Counter() {
// 카운트를 유지하기 위한 자유 변수
var counter = 0;
// 클로저
this.increase = function () {
return ++counter;
};
// 클로저
this.decrease = function () {
return --counter;
};
}
const counter = new Counter();
console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0
생성자 함수 Counter는 increase, decrease 메소드를 갖는 인스턴스를 생성한다. 이 메소드들은 모두 자신이 생성됐을 때의 렉시컬 환경인 생성자 함수 Counter의 스코프에 속한 변수 counter를 기억하는 클로저이며 렉시컬 환경을 공유합니다.
생성자 함수가 함수가 생성한 객체의 메소드는 자신이 기억하는 렉시컬 환경의 변수에도 접근할 수 있는데,
이때 생성자 함수 Counter의 변수 counter는 this에 바인딩된 프로퍼티가 아니라 변수입니다.
counter가 this에 바인딩된 프로퍼티라면 생성자 함수 Counter가 생성한 인스턴스를 통해 외부에서 접근이 가능한 public 프로퍼티가 되지만 생성자 함수 Counter 내에서 선언된 변수 counter는 생성자 함수 Counter 외부에서 접근할 수 없습니다.
하지만 생성자 함수 Counter가 생성한 인스턴스의 메소드인 increase, decrease는 클로저이기 때문에 자신이 생성됐을 때의 렉시컬 환경인 생성자 함수 Counter의 변수 counter에 접근할 수 있습니다.
이러한 클로저의 특징을 사용해 클래스 기반 언어의 private 키워드를 흉내낼 수 있습니다.
Private Method 란?
- 같은 클래스 내부의 특정 메소드에서만 해당 메소드를 호출이 가능한 것을 의미합니다.
- 이를 사용하면 코드의 제한적인 접근만을 제공한다는 장점이 있습니다.
- 전역 네임 스페이스를 관리하여 불 필요한 메서드가 공용 인터페이스를 혼란스럽게 만들지 않도록 합니다.
3. 전역변수 사용 억제
마지막으로 클로저를 사용하면 변수를 공유하는 특성은 유지하되 데이터를 은닉화할 수 있기 때문에, 전역 변수를 대체하여 안전한 코드를 작성할 수 있습니다.
'Javascript' 카테고리의 다른 글
자바스크립트 JavaScript : 실행 컨텍스트(Execution Context) (0) | 2024.04.09 |
---|---|
자바스크립트 JavaScript : 스코프(Scope) (0) | 2024.04.06 |
자바스크립트 JavaScript : 호이스팅(Hoisting) (0) | 2024.04.05 |
자바스크립트 JavaScript : 프로미스에서 유용한 함수 (0) | 2024.04.04 |
자바스크립트 JavaScript : async/await (0) | 2024.04.03 |