13-1. 클로저란 무엇인가?13-1-1. 함수 객체의 내부 슬롯13-2. 클로저와 일반 함수 비교13-2-1. 일반 함수의 특징13-2-2. 클로저의 특징13-3. 클로저의 다양한 활용13-3-1. 데이터 은닉13-3-2. 이벤트 핸들러와 콜백에서의 상태 유지13-3-3. 함수 팩토리13-3-4. 모듈 패턴 13-3-5. setTimeout 과 setInterval에서의 클로저 활용13-3-6. 사용자 테마 설정 유지를 위한 클로저 활용 사례13-4. 클로저의 성능과 최적화13-4-1. 클로저와 메모리 이슈13-4-2. 클로저의 사용 최적화13-4-3. 성능 최적화 팁13-4-4. 클로저를 이용한 반복문과 콜백에서 성능 최적화13-5. 클로저의 장단점13-5-1. 장점과 활용 사례13-5-2. 단점 및 주의 사항
13-1. 클로저란 무엇인가?
클로저는 함수와 그 함수가 선언된 어휘적 환경(lexical environment)의 조합이다. 이를 통해 함수는 자신이 선언될 때의 환경에서 알 수 있었던 변수 중 필요한 변수들을 기억하고 이를 통해 독립적인 환경을 생성한다. 이는 상태를 안전하게 변경하고 유지한다.
간단한 클로저의 예시를 살펴보자
위의 코드에서 outerFunction은 안에 outer라는 변수를 가지고 있고, innerFunction이 그 안에서 정의된다. innerFunction은 outer에 접근할 수 있다. outerFunction은 innerFunction을 반환하고 있으므로, innerFunc 변수에는 innerFunction이 할당되고 마지막으로 innerFunc가 호출될 때, "나는 바깥 변수를 보고 있어"라는 문자열을 출력한다. 이는 innerFunction이 outer 변수를 기억하고 있기 때문에 외부에서 접근할 수 없도록 보호하는 것이 클로저의 핵심 개념 중 하나이다.
클로저는 자바스크립트에서 많이 사용되는 패턴 중 하나이다. 예를 들면, 이벤트 핸들러에서 변수를 보호하기 위해 클로저를 사용하거나, 비동기적인 함수에서 변수를 유지하기 위해 클로저를 사용하는 등 다양한 상황에서 활용된다.
13-1-1. 함수 객체의 내부 슬롯
함수 객체의 내부 슬롯 [[Environment]]은 함수가 선언될 때의 환경을 말한다. 이 환경은 스코프 체인을 구성하며, 함수가 호출될 때마다 해당 환경을 참조한다.
자바스크립트에서 함수는 일급 객체로 취급되기 때문에 다른 객체와 마찬가지로 속성 값을 가질 수 있다. 이러한 속성 중에서도 가장 중요한 것이 [[Environment]] 내부 슬롯이다. [[Environment]] 내부 슬롯은 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)을 가리키며, 스코프 체인을 구성하는 중요한 역할을 한다.
스코프 체인이란 변수를 검색할 때 참조하는 중첩된 렉시컬 환경의 목록이다. 예를 들어, 함수 내부에서 변수를 참조하면 자바스크립트 엔진은 먼저 함수 내부의 렉시컬 환경에서 변수를 검색하고, 만약 해당 변수를 찾지 못하면 외부 렉시컬 환경에서 변수를 검색한다. 이렇게 스코프 체인을 통해 변수를 검색하면서, [[Environment]] 내부 슬롯이 참조하는 렉시컬 환경 내부에 있는 변수에 접근할 수 있다.
클로저란 함수와 그 함수가 선언될 때의 렉시컬 스코프 사이의 조합으로, 함수가 자신이 생성될 때의 스코프에서 알 수 있었던 변수들 중 언젠가 자신이 실행될 때 사용할 변수들만 기억하고 있다. 이는 함수가 수행될 때 독립적인 환경을 만들어 줄 수 있고, 이를 통해 상태를 안전하게 변경하고 유지할 수 있다.
따라서, [[Environment]] 내부 슬롯은 함수의 동작 방식을 결정하는 중요한 역할 한다. 함수가 호출될 때마다 해당 환경을 참조하는 것을 통해, 클로저를 구현하고 상태를 안전하게 유지할 수 있다. 이를 통해 자바스크립트에서 함수를 이용한 다양한 기능들을 구현할 수 있다.
클로저와 가비지 컬렉션
가비지 컬렉션 (Garbage Collection)
JavaScript는 메모리 관리를 자동으로 한다. 이를 위해 사용되는 기술 중 하나가 가비지 컬렉션이다. 객체가 더 이상 사용되지 않을 때, 즉 어떠한 참조도 가리키지 않을 때 해당 객체는 메모리에서 제거된다.
클로저는 외부 함수의 변수에 대한 참조를 유지한다. 따라서 클로저가 존재하는 한, 클로저에 의해 참조되는 변수나 객체는 메모리에서 해제되지 않는다. 이는 때로는 의도치 않게 메모리 누수(memory leak)의 원인이 될 수 있다.
위의 코드에서 holdReference는 BigObject 함수의 반환 값인 클로저를 참조하고 있다. 따라서 bigArray는 holdReference가 존재하는 한 메모리에서 해제되지 않는다.
이런 경우, holdReference를 null로 설정하면 bigArray에 대한 참조가 끊어져 가비지 컬렉션의 대상이 된다.
이처럼 클로저는 메모리 누수의 위험이 있으므로, 해당 클로저가 어떤 객체나 변수를 참조하고 있는지, 그리고 그 참조가 언제 끊어질지 항상 염두에 두어야 한다.
13-2. 클로저와 일반 함수 비교
13-2-1. 일반 함수의 특징
일반 함수(Regular Function)는 입력된 인자를 기반으로 연산을 수행하고, 결과를 반환하는 코드 블록 이다. 이 함수는 호출될 때마다 자신의 지역적인 실행 환경을 가진다.
위의 예제에서 add는 일반 함수이다. 각각의 호출에서 지역 변수 a와 b를 사용하여 연산을 수행한다.
13-2-2. 클로저의 특징
클로저는(Closure)는 함수와 그 함수가 선언된 환경을 함께 가지고 있는 코드 블록이다. 즉, 클로저는 자신이 생성된 환경의 변수를 참조할 수 있다.
위의 예제에서 innerFunction은 클로저이다. outerFunction의 지역 변수 outerVariable에 접근할 수 있다. outerFunction이 실행을 완료한 후에도 innerFunction은 outerVariable에 접근할 수 있기 때문에 클로저라고 한다.
일반 함수: 인자를 받아 연산을 수행하고 결과를 반환하는 코드 블록이다.
클로저: 외부 함수의 변수에 접근할 수 있는 내부 함수입니다. 클로저는 자신이 생성된 환경의 정보를 '기억'한다.
클로저는 주로 데이터를 은닉하거나, 상태를 유지하는 함수를 만들 때 사용된다.
13-3. 클로저의 다양한 활용
클로저는 자바스크립트에서 매우 중요한 개념으로, 여러 가지 방법으로 활용된다. 클로저의 사용 방법에 대한 몇 가지 일반적인 사례를 아래에 정리하였다.
13-3-1. 데이터 은닉
데이터 은닉(Data Encapsulation)이란 클로저를 이용하여 특정 변수나 데이터를 외부로부터 보호하는 기법이다.
13-3-2. 이벤트 핸들러와 콜백에서의 상태 유지
클로저는 이벤트 핸들러나 비동기 콜백에서 상태 정보를 유지하는 데 사용될 수 있다.
13-3-3. 함수 팩토리
함수 팩토리(Function Factory)는 클로저를 사용해 동적으로 함수를 생성한다.
13-3-4. 모듈 패턴
클로저를 이용해 자바스크립트에서 공개/비공개 메서드와 변수를 가진 모듈 패턴(Module Pattern)을 구현할 수 있다.
13-3-5. setTimeout 과 setInterval에서의 클로저 활용
자바스크립트의 타이머 함수인 setTimeout 과 setInterval에서 클로저를 활용하여 값을 보존할 수 있다.
1. setTimeout함수를 사용한 간단한 알림 예제:
이 예제에서 message 변수는 외부 함수 notifyAfterSeconds의 매개변수다. setTimeout 내의 함수는 이 변수를 기억하고 있다.
2. setInterval 함수를 사용한 타이머 예제:
위 예제는 5초 동안 1초마다 "Time left: x seconds"를 출력하고, 시간이 다 되면 "Time is up"을 출력하고 타이머를 종료한다.
예제에서 timeLeft와 interval은 외부 함수 startTimer의 지역 변수이며, setInterval 내부의 함수는 이 변수들에 접근할 수 있다.
이는 클로저의 특징이다. 이러한 예제들을 통해 setTimeout과 setInterval 내부의 함수가 외부 함수의 변수를 '기억'하고 있음을 볼 수 있다.
이렇게 클로저는 다양한 상황에서 활용됩니다. 클로저를 사용함으로써 변수와 함수의 범위와 생명주기를 더욱 유연하게 관리할 수 있다.
13-3-6. 사용자 테마 설정 유지를 위한 클로저 활용 사례
다음은 웹페이지의 다크모드와 라이트모드를 전환하면서 사용자의 선택을 기억하는 클로저의 예시입니다.
이 코드는 themeMode 라는 외부 함수를 통해 클로저를 생성합니다. 이 클로저는 내부의 switchMode와 currentMode 함수가 mode라는 외부 변수를 기억하도록 합니다. 사용자가 모드를 변경할 때 함수를 호출하면, 변수의 값을 변경하고 이 변화를 웹페이지에 적용합니다. 함수는 현재 설정된 모드를 반환합니다.
13-4. 클로저의 성능과 최적화
클로저는 다양한 프로그래밍 패턴과 기법에서 광범위하게 사용된다. 그러나 클로저를 사용할 때 메모리와 성능에 관련된 문제에 주의해야 한다. 이번 섹션에서는 클로저의 주요 성능 문제점과 그 문제를 해결하기 위한 최적화 방법에 대해 알아보자.
13-4-1. 클로저와 메모리 이슈
클로저는 내부 슬롯 [[Environment]] 때문에 해당 클로저가 참조하는 외부 변수들을 계속 유지되고 이로 인해 해당 변수들이 가비지 컬렉션에서 제외되며, 메모리 누수를 발생시킬 수 있다.
13-4-2. 클로저의 사용 최적화
불필요한 클로저 제거: 클로저를 만들 필요가 없는 상황에서는 클로저를 사용하지 않는 것이 좋다. 이는 불필요한 메모리 점유를 줄일 수 있다.
클로저의 수명: 클로저의 참조를 제거하면 가비지 컬렉터가 해당 클로저와 그 클로저가 참조하는 외부 변수들을 정리할 수 있다. 불필요한 클로저는 참조를 제거해 주는 것이 좋다.
13-4-3. 성능 최적화 팁
반복문 내 클로저: 반복문 내 클로저 사용 시 주의해야 한다. 클로저가 반복문 변수를 참조하면 예기치 않은 결과가 발생할 수 있다. 이를 방지하기 위해 추가 스코프를 제공하는 즉시 실행 함수(IIFE) 패턴 등을 사용할 수 있다.
이벤트 핸들러의 클로저: 이벤트 핸들러에서 클로저를 사용할 때는 이벤트 리스너를 제거하거나, 클로저 대신 객체의 메서드나 데이터 속성을 사용하여 상태를 관리하는 방법을 고려할 수 있다.
13-4-4. 클로저를 이용한 반복문과 콜백에서 성능 최적화
반복문과 클로저의 조합은 자바스크립트에서 흔히 발생하는 문제 중 하나다. 특히 비동기 콜백을 반복문 내에서 사용할 때 발생하는 문제를 예로 들 수 있다.
1. 문제 발생 예시
setTimeout이나 이벤트 핸들러와 같은 비동기 콜백을 반복문 내에서 사용할 때, 클로저의 특성으로 인해 의도치 않은 결과가 발생할 수 있다.
이 문제의 원인은 var로 선언된 i변수가 전역 스코프에 존재하며, setTimeout의 콜백 함수가 이 변수를 참조하는 클로저를 형성하고 반복문이 완료된 후 i의 값은 5이므로, 모든 콜백에서 5를 출력한다.
2. 해결 방법
2.1. IIFE (즉시 실행 함수 표현)
IIFE를 사용하면 반복문의 각 반복에 대해 새로운 스코프를 생성하여 문제를 해결할 수 있다.
2.2. let 키워드 사용
ES6에서 도입된 let 키워드는 블록 스코프 변수를 선언한다. 이를 사용하면 각 반복에서 새로운
i
의 복사본이 생성된다.13-5. 클로저의 장단점
클로저는 함수형 프로그래밍에서 중요한 개념 중 하나로, 다양한 유용한 패턴을 구현하는 데 사용된다. 클로저에도 장단점이 있다. 이러한 장단점을 이해하면 클로저를 더 효과적으로 사용할 수 있다.
13-5-1. 장점과 활용 사례
클로저의 장점
- 데이터 은닉 및 캡슐화: 클로저를 사용하면 외부에서 접근할 수 없는 private 변수를 만들 수 있다. 이를 통해 데이터의 무결성을 보장할 수 있다.
- 동적 함수 생성: 실행 시점에 동적으로 함수를 생성하고 수정하는 것이 가능하다. 이러한 유연성은 고차 함수나 함수 팩토리에서 특히 유용하다.
- 상태 보존: 클로저는 상태를 "기억"하는 함수를 만들 수 있다. 이를 통해 이벤트 리스너, 콜백 함수 등에서 필요한 상태 정보를 유지하면서 동작을 수행할 수 있다.
- 콜백과 비동기 작업: 클로저를 사용하면 비동기 작업이나 콜백 함수에서 필요한 환경 정보나 상태를 캡쳐하여 사용할 수 있다.
클로저의 활용 사례
- 이벤트 핸들러: 클로저를 사용하여 이벤트 핸들러를 구현하면, 이벤트 발생 시 필요한 상태 정보를 유지하면서 동작을 수행할 수 있다.
- 콜백 함수: 클로저를 사용하여 콜백 함수를 구현하면, 콜백 함수가 호출될 때 필요한 상태 정보를 유지할 수 있다.
- 고차 함수: 클로저를 사용하여 고차 함수를 구현하면, 함수의 인자로 함수를 전달하거나 함수의 결과로 함수를 반환할 수 있다.
13-5-2. 단점 및 주의 사항
클로저의 단점
- 메모리 누수: 클로저는 외부 스코프의 변수를 참조할 수 있다. 이에 따라 해당 변수는 가비지 컬렉션 대상에서 제외될 수 있어 메모리 누수가 발생할 가능성이 있다.
- 오버헤드: 과도한 클로저 사용은 코드의 복잡성을 증가시킬 수 있다. 또한, 클로저를 생성하고 관리하는 데에는 추가적인 오버헤드가 발생할 수 있다.
- 스코프 체인의 복잡성: 클로저는 여러 스코프 체인을 통해 변수를 참조할 수 있다. 이로 인해 예상치 못한 부작용이나 버그가 발생할 수 있다.
클로저 사용 시 유의 사항
- 메모리 누수 방지: 클로저가 참조하는 외부 변수가 더 이상 필요하지 않으면, 해당 변수를 명시적으로 해제해야 한다.
- 코드 복잡성 최소화: 과도한 클로저 사용은 코드의 복잡성을 증가시킬 수 있으므로, 클로저 사용을 최소화해야 한다.
- 스코프 체인 이해: 클로저는 여러 스코프 체인을 통해 변수를 참조할 수 있으므로, 스코프 체인을 올바르게 이해해야 한다.