자바스크립트에는 접근 제한자(private) 키워드가 없기 때문에, 외부에서 직접 접근할 수 없는 변수를 만들려면 클로저(Closure)를 활용하는 것이 대표적인 방법입니다. 함수 내부에 선언된 변수는 함수 외부에서 접근할 수 없지만, 그 내부에서 반환된 함수는 해당 변수에 대한 참조를 유지합니다. 이러한 특성을 이용하면 변수를 안전하게 숨기면서도 제어된 방식으로만 조작할 수 있습니다.
핵심 개념
- 외부 함수가 실행되고 종료된 후에도, 내부 함수는 외부 함수의 스코프에 있는 변수를 기억하고 접근할 수 있습니다.
- 따라서 변수를 외부 함수 안에 선언하고, 그 변수를 다루는 함수만 반환하면 외부 코드는 오직 반환된 함수를 통해서만 변수에 접근할 수 있습니다.
예제 코드
아래 예제는 클로저를 사용해 카운터 변수를 은닉하는 방법을 보여줍니다. 'Increment' 버튼은 클로저를 통해 변수를 증가시키고, 'Access' 버튼은 변수에 직접 접근을 시도하여 에러가 발생하는 것을 확인할 수 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
.result {
font-size: 18px;
font-weight: 500;
color: blueviolet;
}
</style>
</head>
<body>
<h1>Using closures to achieve privacy</h1>
<div class="result"></div>
<br />
<button class="Btn">Increment</button>
<button class="Btn">Access</button>
<h3>Click on the above button to increment or try to access the variable</h3>
<script>
let resEle = document.querySelector(".result");
let BtnEle = document.querySelectorAll(".Btn");
function increment() {
let a = 0;
return function () {
a++;
return a;
};
}
let inc;
inc = increment();
BtnEle[0].addEventListener("click", () => {
resEle.innerHTML = inc();
});
BtnEle[1].addEventListener("click", () => {
try {
resEle.innerHTML = a;
} catch (err) {
resEle.innerHTML = err;
}
});
</script>
</body>
</html>실행 결과
'Increment' 버튼을 4번 클릭하면, 클로저가 유지하고 있는 변수 값이 매번 1씩 증가하여 화면에 순차적으로 표시됩니다. 이때 변수 a는 increment 함수의 스코프 안에 숨겨져 있으므로, 외부에서는 inc() 함수를 통해서만 값을 변경할 수 있습니다.
반면 'Access' 버튼을 클릭하면 전역 스코프에는 변수 a가 존재하지 않기 때문에 ReferenceError가 발생하며, 이 에러 메시지가 화면에 출력됩니다. 이는 클로저를 통해 변수가 성공적으로 은닉되었음을 보여주는 결과입니다.
정리
이처럼 클로저를 사용하면 자바스크립트에서도 private 변수와 유사한 캡슐화를 구현할 수 있습니다. 상태를 외부로부터 보호하고, 의도된 인터페이스(반환된 함수)를 통해서만 상태를 변경하도록 강제함으로써 더 안전하고 예측 가능한 코드를 작성할 수 있습니다.