클로저란
클로저는 외부 함수에서 선언된 변수를 참조하는 내부 함수에서만 발생하는 현상이다. MDN에선 클로저를 "함수와 그 함수가 선언된 Lexical Environment의 조합"라 정의한다. Lexical Environment는 함수가 선언될 당시의 모든 지역 변수를 포함한다. 선언된 당시의 Lexical Environment이란 실행 컨텍스트의 구성 요소 중 하나인 outerEnvironmentReference를 포함하여 변수의 유효 범위(스코프)를 결정하고 스코프 체인을 형성하는 데 영향을 끼친다. JavaScript에서 클로저는 함수 생성 시 함수가 생성될 때마다 생성된다.
클로저의 범위
클로저는 어휘적 범위 지정, 즉 함수가 선언될 때의 변수 범위에 바인딩되는 특성을 가지고 있다. 예를 들어, 외부 함수에서 선언된 변수를 내부 함수가 참조하면 외부 함수의 실행 컨텍스트가 종료된 후에도 해당 변수가 소멸되지 않는 현상이 발생한다. 예를 들어, 외부 함수 A 내에서 선언된 내부 함수 B의 실행 컨텍스트가 활성화될 때, B의 outerEnvironmentReference는 A의 Lexical Environment에 접근할 수 있다. 이는 B가 A에서 선언된 변수들에 접근할 수 있음을 의미하며, 반대로 A는 B에서 선언된 변수에는 접근할 수 없다.
JavaScript의 ES6 버전부터는 스코프의 개념이 확장되어, ES5에서 존재하던 전역 스코프, 함수 스코프 외에도 블록 스코프가 추가된다. 예를 들어, 함수 스코프는 특정 함수 내에서 선언된 변수들이 그 함수 내에서만 접근 가능함을 의미한다. 아래는 함수 스코프의 예제 코드이다.
function MyObject(name) {
this.name = name
this.myMethod = function() {
return this.name
};
}
반면에 var 키워드를 사용하여 선언된 변수는 중괄호로 구분된 블록 내에 있더라도 전역 스코프에 속하게 된다. 이 혼란을 피하기 위해 이는 let과 const 키워드로 변수들을 생성하면 블록 스코프를 생성할 수 있다.아래의 예제를 보면 var 키워드와 let, const 키워드로 생성되는 스코프의 차이에 대해 알 수 있다.
if(true){
var x = 1; // 전역 스코프
}
console.log(x) // 1
if(true){
let x = 1; // 블록 스코프
}
console.log(x) // 1
클로저의 메모리 관리
클로저는 외부 함수가 종료된 후에도 외부 함수의 지역 변수에 접근할 수 있게 하기에 외부함수의 지역변수가 메모리에 남아 있어 메모리 소모를 일으킨다. 내부 함수가 외부 함수의 변수에 대한 참조를 가지고 있기 때문에 이러한 변수들은 가비지 컬렉션의 대상이 되지 않다.
클로저가 참조하는 변수들이 더 이상 필요하지 않게 되면 자바스크립트 엔진은 이를 감지하고 메모리를 해제한다. 그렇다 하더라도 특정 작업에 클로저가 필요하지 않는데 다른 함수 내에서 함수를 불필요하게 작성하는 것은 메모리 누수를 일으킬 수 있기에 메서드를 생성할 때 객체 생성자가 아닌 프로토타입에 추가하여 클로저를 사용하지 않을 수 있다.
// 객체 생성자로 메서드 생성: 클로저생성
function MyObject(name) {
this.name = name
this.myMethod = function() {
return this.name
};
}
// 프로토 타입에 메서드 추가: ~클로저생성
function MyObject(name) {
this.name = name
}
MyObject.prototype.myMethod = function() {
return this.name
};
클로저의 활용
클로저는 다양한 상황에서 유용하게 사용될 수 있다. 예를 들어, 클로저를 활용하여 콜백 함수 내에서 외부 변수를 참조하거나 정보를 은닉하고 부분 적용 함수나 커링 함수를 구현할 수 있다.
비공개 메서드
클로저를 활용하여 자바스크립트에서 비공개(Private) 메서드와 변수를 흉내내어 모듈 디자인 패턴을 구현할 수 있다. 모듈 디자인 패턴은 함수 내에서 데이터와 메서드를 캡슐화하여 외부에서 직접적인 접근을 제한함으로써 정보 은닉과 캡슐화의 원칙을 따른다.
모듈 디자인 패턴은 즉시 실행 함수(IIFE: Immediately Invoked Function Expression)를 사용하여 구현된다. 이 패턴은 외부에서 접근할 수 없는 비공개 변수와 함수를 생성하고, 공개적으로 사용하는 퍼블릭 함수를 반환한다. 아래는 모듈 디자인 패턴을 사용하여 클로저를 구현한 예시 코드이다
const Module = (function() {
// 비공개 변수
const privateConst = "비밀";
// 비공개 함수
function privateMethod() {
console.log("비공개 메서드에 접근");
return privateConst;
}
return {
// 퍼블릭 메서드
publicMethod: function() {
console.log("퍼블릭 메서드에 접근");
return privateMethod();
}
};
})();
console.log(Module.publicMethod());
// 퍼블릭 메서드에 접근
// 비공개 메서드에 접근
// 비밀
부분 적용 함수
클로저를 활용하여 함수형 프로그래밍인 부분 적용 함수를 만들 수 있다. 부분 적용 함수는 주어진 함수의 일부 인자만 미리 적용하고, 나머지 인자를 나중에 적용할 수 있는 새로운 함수를 생성하는 기법이다.
아래 코드는 두 인자를 더하는 함수에서 부분적용으로 x를 5로 미리 선언하고 나머지 하나의 인자를 받아 5와 더하는 부분 적용 함수이다. 클로저를 활용하여 내부 함수에서 외부 함수의 변수인 x를 가져옴을 볼 수 있다.
function sum(x) {
return function (y) {
return x + y;
};
}
const add5 = sum(5);
console.log(add5(2)); // 7
커링 함수
커링은 함수와 함께 사용하는 기법이다. 커링은 f(a, b, c)처럼 단일 호출로 처리하는 함수를 f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합되도록 변환하는 것이다. 커링은 함수를 호출하지 않고 변환만 한다.
다음은 클로저를 활용하여 커링 변환을 하는 curry 함수를 구현한 예제 코드이다.
function curry(f) { // 커링 변환을 하는 curry 함수
return function(a) {
return function(b) {
return f(a, b);
};
};
}
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
참고자료
Javascript.info. (n.d.). 커링.
Mozilla. (n.d.). Closures - JavaScript. MDN Web Docs.
정재남. (2019). 코어 자바스크립트. 위키북스.
'JS' 카테고리의 다른 글
[JS] Javascript에서의 Memory Management (0) | 2024.07.18 |
---|---|
[JS] Javascript의 프로토타입과 상속: 개념과 동작 이해 (0) | 2024.01.12 |
[JS] Javascript에서의 this (1) | 2024.01.09 |
[JS] 반복문 대신 고차함수를 쓰는 이유 (0) | 2024.01.07 |
[JS] 클린코드를 위해 반복문과 조건문 줄이기(feat: 들여쓰기) (0) | 2024.01.07 |