클로저는 자바스크립트에서 중요한 개념 중 하나이며, 자바스크립트를 공부하는 사람이라면 한번쯤은 들어봤을 단어이다. 클로저는 실행 컨텍스트와 관련있는 개념이며, 이에 대해 자세히 살펴보자.
반환된 내부함수가 자신이 선언되었을 때의 환경(스코프)을 기억하여 해당 스코프 밖에서 호출되어도 그 환경에 접근할 수 있는 함수를 말한다.
즉, 클로저는 자신이 생성될 때의 환경인 Lexical environment
을 기억하는 함수이다.
흔히, 함수 내에서 함수를 정의하고 사용하면 대체로 클로저라고 한다.
예제 코드를 살펴보자. 👇
function getClosure(){
let text = "text";
return function(){
return text;
};
}
let closure = getClosure();
console.log(closure());
위에서 정의한 getClosure( )
는 함수를 반환하고, 반환된 함수는 getClosure() 내부에서 선언된 변수 text
를 참조하고 있다.
하지만, closure의 변수가 선언되어 있는 환경은 getClosure( )와 다르지만, text 변수에 대한 참조가 정상적으로 작동하는 것을 확인할 수 있다.
우리는 이러한 함수를 클로저
라고 부르는 것이다.
또 다른 예제를 살펴보자. 👇
let base = "Hi, ";
function sayHiTo(name){
let text = base + name;
return function(){
console.log(text);
};
}
let person1 = sayHiTo("seohee");
let person2 = sayHiTo("jungmin");
person1();
person2();
text 변수가 동적으로 변화하는 것처럼 보이지만, 실제로는 person1( )과 person2()가 서로 다른 환경
을 가지고 있는 것이다.
즉, text라는 변수 자체가 한 번이 아닌 여러 번 생성
된 것이다.
그렇다면, 클로저를 사용하는 이유는 무엇일까?🧐
Java와 달리,
JavaScript
는private를 구현
하기 위한 방법을 제공하지 않기에 closure를 사용한다.
즉, closure는 data와 함수를 연결시켜준다.
이는 data와 하나 또는 여러개의 method와 연결되어있는 OOP(object-oriented-programming)과 같다고 볼 수 있으며, 결국 closure를 사용하여 OOP의 object로 이용할 수 있다.
일반적으로 JavaScript에서 말하는 객체지향 프로그래밍
이란 Prototype
을 통해 객체를 다루는 것을 말한다.
Prototype
을 통해 객체를 만들 때는 Private variables
에 대한 접근 권한 문제를 생각하며, 코드를 만들어야 한다.
예제 코드를 보자.👇
function Hi(name){
this._name = name;
}
Hi.prototype.say = function(){
console.log("Hi, " + this._name);
}
let person1 = new Hi("seohee");
let person2 = new Hi("jungmin");
person1.say();
person2.say();
person1._name = "SEOHEE";
person1.say();
Hi( )로 생성된 객체 person1, person2
는 모두 _name
이라는 변수를 가지게 된다.
하지만, 이는 함수 내부에서만 호출되는 것이 아니라 여전히 외부에서도 접근 가능
한 변수라는 것을 결과를 통해 알 수 있다.
이러한 경우, 클로저를 사용해서 외부에서 접근 불가능하게 만드는 것이 가능하다.👇
function Hi(name){
let _name = name;
return function(){
console.log("Hi, " + _name);
};
}
let person1 = Hi("seohee");
let person2 = Hi("jungmin");
person1();
person2();
person1._name;
//메모리 release를 시켜 클로저의 참조 제거
person1 = null;
person2 = null;
이렇게 외부에서 _name
에 접근할 수 없도록 만들어 정보의 은닉화
를 구현하였다.
또한, 마지막에는 메모리를 해제하기 위해 null값을 할당시켜 참조를 제거하였다.
가장 유용하게 사용되는 상황이
현재 상태를 기억하고 변경된 최신 상태를 유지
하는 것이다.
예제 코드를 보자.👇
<!DOCTYPE html>
<html>
<body>
<button class="toggle">toggle</button>
<div class="box" style="width: 100px; height: 100px; background: red;"></div>
<script>
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;
</script>
</body>
</html>
1번에서 즉시실행함수는 함수를 반환하고 바로 소멸된다.
이 함수가 반환한 함수는 자신이 생성되었을 때의 렉시컬 환경에 속하는 변수 isShow
를 기억하는 함수이다.
2번에서 해당 함수를 이벤트 핸들러로서 사용하기에 isShow라는 변수는 사라지지 않는다.
따라서 toggle버튼을 클릭
하면,클로저가 호출
되고, isShow의 값이 변경된다.
변수 isShow는 클로저에 의해 참조되고 있기 때문에, 유효한 변수이며 변경된 최신 상태
를 계속해서 유지한다.
결론적으로 클로저의 기능이 존재하지 않는다면, 자바스크립트에서는 상태를 유지하기 위해 전역 변수를 사용할 수 밖에 없다.
하지만, 전역변수는 언제 어디서나 누구든지 접근/변경이 가능하기에 많은 오류의 원인이 될 수 있다.
따라서 우리는 전역변수의 사용을 억제하고, 클로저를 사용하는 방향을 지향해야 한다.
버튼을 클릭할 때마다, 이에 대한 값을 누적해서 보여주는 카운터를 구현해보자.
유지해야 할 상태는 클릭된 횟수
이다.
<!DOCTYPE html>
<html>
<body>
<p>전역 변수를 사용한 Counting</p>
<button id="inclease">+</button>
<p id="count">0</p>
</body>
</html>
var incleaseBtn = document.getElementById('inclease');
var count = document.getElementById('count');
// 카운트 상태를 유지하기 위한 전역 변수
var counter = 0;
function increase() {
return ++counter;
}
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
해당 코드에서 문제점은 counter의 값이 반드시 0이어야 제대로 동작한다는 것이다.
왜냐하면, counter변수는 전역 변수이기에 누구나 접근/변경이 가능하고 이는 의도하지 않게 값이 변경되어 오류로 이어지기 때문이다.
따라서 counter는 increase 함수가 관리해야 한다는 필요성을 알 수 있으며
아래 코드에서 전역 변수를 increase 함수의 지역 변수로 바꿔보도록 하자.
var incleaseBtn = document.getElementById('inclease');
var count = document.getElementById('count');
function increase() {
// 카운트 상태를 유지하기 위한 지역 변수
var counter = 0;
return ++counter;
}
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
counter를 increase함수 안으로 넣어 지역변수로 바꿔주었다. 의도치 않은 상태변경에 대한 오류는 해결하였지만, increase함수가 호출될 때마다 지역변수 counter를 0으로 초기화되는 또다른 오류를 범하였다.
다시 말해 변경된 이전 상태를 기억하지 못한다
는 것이다.
이 문제를 해결한 다음 코드이다. 👇
var incleaseBtn = document.getElementById('inclease');
var count = document.getElementById('count');
var increase = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
var counter = 0;
// 클로저를 반환
return function () {
return ++counter;
};
}());
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
해당 파일이 실행되면, 즉시 실행함수가 호출되고, incerase에는 렉시컬 환경을 기억하는 클로저를 할당하였다.
즉시실행함수가 반환한 함수가 increase에 할당되어 있기 때문에, 버튼을 클릭하면 클릭 이벤트 핸들러 내부에서 호출된다.
또한, 즉시실행함수는 한번만 실행
되므로 increase가 호출될 때마다 counter가 초기화될 일도 발생하지 않는다.
따라서 우리는 counter를 외부에서 직접 접근할 수 없는 private
한 변수로 설정해주었고, 의도하지 않은 변경 또한 막아주었음을 확인할 수 있다.
결론적으로 클로저는
불변성을 지향
하는 함수형 프로그래밍에서부수 효과를 최대한 억제
하여 오류룰 피하고 프로그래밍의안전성을 높이기
위해서 적극적으로 사용되는 개념이다.
📚 학습할 때, 참고한 자료 📚