자바스크립트 클로저가 대체 뭐야?!
JavaScript
2024-08-07
프론트엔드 면접을 준비해본 사람이라면 자바스크립트 클로저에 대해서 한번쯤은 들어봤을것 입니다.
하지만 처음 접했을때는 난해한 개념이라고 생각하여 그냥 넘어간 사람도 많을것 입니다.
사실 클로저는 자바스크립트만의 고유의 개념이 아닙니다. 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(하스켈, 리스프, 얼랭, 스칼라 등)에서 사용되는 중요한 특성입니다.
1. 렉시컬 스코프
클로저의 개념을 더 잘 이해하기 위해서는 먼저 렉시컬 스코프에 대해 알아야 합니다.
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 어디에 정의 했는지에 따라 상위 스코프를 결정하는데 이것을 렉시컬 스코프라고 합니다.
예제를 통해 렉시컬 스코프를 살펴보겠습니다.
const x = 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
위 예제의 foo
함수와 bar
함수는 모두 전역에서 정의된 전역 함수입니다.
함수의 상위 스코프는 함수를 어디서 정의했느냐에 따라 결정되므로 foo
함수와 bar
함수의 상위 스코프는 전역입니다. 함수를 어디서 호출하는지는 상위 스코프 결정에 영향을 주지 못합니다.
즉, 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않습니다. 이처럼 렉시컬 스코프는 함수가 정의된 시점의 환경을 기반으로 변수를 해석합니다.
2. 클로저(Closure)의 정의
이제 클로저의 정의를 살펴보겠습니다.
: 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
위 정의는 함수를 호출할 때 함수가 선언된 환경을 '기억'하고, 그 환경에 접근할 수 있는 특성을 의미합니다.
예제와 함께 정의를 이해해 보겠습니다.
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
}
return innerFunc;
}
const myFunc = outerFunc();
myFunc(); // 10
이 예제에서 outerFunc는 innerFunc를 반환하고, 반환된 innerFunc를 myFunc 변수에 할당합니다. 이후 myFunc를 호출할 때, innerFunc는 outerFunc의 스코프를 기억하여 x 변수에 접근할 수 있습니다. 이 경우 innerFunc는 클로저로 작동합니다.
innerFunc가 클로저인 이유는, 함수가 outerFunc 내부에서 정의되었기 때문에 outerFunc의 스코프 내 변수 x에 접근할 수 있기 때문입니다.
클로저는 이렇게 함수가 선언된 렉시컬 환경을 기억
하여, 함수가 호출된 이후에도 그
환경에 접근할 수 있게 합니다.
3. 클로저의 특성
3-1. 데이터 보존
클로저 함수는 외부 함수의 실행이 끝나더라도 외부 함수 내 변수를 사용할 수 있습니다. 클로저는 이처럼 특정 데이터를 스코프 안에 가둔 채로 계속 사용할 수 있게 하는 폐쇄성을 갖습니다.
이를 통해 함수가 호출될 때마다 동일한 환경에서 작업을 수행할 수 있습니다.
3-2. 정보의 접근 제한(캡슐화)
클로저를 사용하면 외부에서 접근할 수 없는 변수를 만들 수 있습니다. 클로저 모듈 패턴을 사용해 객체에 담아 여러 개의 함수를 반환하도록 만들 수 있으며, 이를 통해 접근을 제한하는 캡슐화를 구현할 수 있습니다.
캡슐화는 데이터 보호와 코드의 안전성을 높이는 데 유용합니다.
3-3. 모듈화에 유리
클로저 함수를 각각의 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있습니다. 이와 같이 함수의 재사용성을 극대화하여 함수 하나를 독립적인 부품의 형태로 분리하는 것을 모듈화라고 합니다.
클로저는 데이터와 메소드를 묶어 다닐 수 있기에 모듈화에 매우 유리합니다.
4. 실용적인 사용 사례
개인 정보 보호
클로저는 변수에 대한 접근을 제한하여 데이터 은닉을 구현할 수 있습니다. 예를 들어, 비밀번호와 같은 민감한 정보를 외부에서 접근하지 못하게 할 수 있습니다.
function createUser(name, password) {
return {
getName: function () {
return name;
},
validatePassword: function (inputPassword) {
return inputPassword === password;
},
};
}
const user = createUser("Alice", "secret");
console.log(user.getName()); // Alice
console.log(user.validatePassword("secret")); // true
console.log(user.validatePassword("wrongPassword")); // false
console.log(user.password); // undefined
함수 팩토리
클로저를 이용하여 여러 설정을 가진 함수를 생성할 수 있습니다. 예를 들어, 특정 값을 곱해주는 함수를 생성하는 팩토리를 만들 수 있습니다.
function createMultiplier(multiplier) {
return function (num) {
return num * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
createMultiplier 함수가 실행된 후에도 내부 함수가 multiplier 값을 기억하고 있습니다. 때문에 이것은 클로저의 특성을 이용한것 입니다.
타이머와 비동기 처리
클로저는 타이머와 비동기 처리에서도 유용하게 사용됩니다. 예를 들어, 일정 시간이 지난 후에 특정 작업을 수행할 때 클로저를 활용할 수 있습니다.
function createTimer(message, delay) {
setTimeout(function () {
console.log(message);
}, delay);
}
createTimer("1초 후에 실행됩니다.", 1000);
createTimer("2초 후에 실행됩니다.", 2000);
이 밖에도 초기화 코드나, 함수 커링등 클로저의 특성을 이용하여 다양한 사용 사례들이 있습니다.
결론
오늘은 클로저에 대해 알아보았습니다.
클로저는 자바스크립트의 함수형 프로그래밍 패러다임에서 중요한 역할을 하며, 잘 활용하면 코드의 품질과 효율성을 크게 향상 시킬 수 있습니다. 클로저의 개념을 이해하고 적절히 활용함으로써, 더 안전하고 유지보수하기 쉬운 애플리케이션을 개발할 수 있을것 입니다.
참고
모던 자바스크립트 Deep Dive 24장 클로저