어제부터 매일메일이라는 서비스를 구독해서 매일 아침마다 기술 질문을 하나씩 받기 시작했습니다.
첫날 질문이 클로저가 무엇인지에 대한 질문이었습니다. 이 정도 쯤이야 당연히 알고 있다고 생각하고 메모장에 적어두고 답과 비교해보았는데, 생각보다 잘 정리된 답변에 "아 이 정도까지는 대답할 수 있어야 면접에서 합격하겠구나" 라는 생각이 들어서 클로저에 대해서 이해하고 정리해보려 합니다.
매일메일에서는 클로저를 다음과 같이 정의합니다.
클로저는 자바스크립트의 함수가 일급 객체라는 특성과 렉시컬 스코프의 조합으로 만들어집니다.
클로저를 이해하기 위해서 먼저 일급 객체와 렉시컬 스코프부터 알아볼 것 입니다.
함수가 일급 객체라는 특성은 자바스립트에서 함수를 다른 변수나 객체처럼 취급할 수 있다는 뜻입니다. 일급 객체는 다음과 같은 특징을 가집니다.
// 1. 변수에 할당할 수 있다.
const greet = function(name) {
return `Hello, ${name}`;
}
// 2. 함수를 인자로 전달할 수 있다.
function executeFunction(func, value) {
return func(value);
}
// 3. 함수에서 함수를 반환할 수 있다.
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
자바스크립트를 사용해보았다면 위와 같은 방법으로 함수를 사용한 경험이 있을 것입니다. 이는 자바스크립트에서 함수가 일급 객체이기 때문에 가능한 것입니다.
렉시컬 환경은 실행 컨텍스트를 구성하는 중요한 내부 메커니즘으로, 이는 변수와 함수 선언이 코드 내에서 어떻게 저장되고 접근되는지를 정의합니다. 이런 참조 메커니즘 덕분에 함수는 자신이 생성된 위치의 스코프를 기억할 수 있고 내부 함수는 외부 함수의 변수에 접근할 수 있습니다.
아래 블로그에서 실행 컨텍스트와 렉시컬 환경에 대해서 더 자세하게 알아볼 수 있습니다.
자바스크립트 - 실행 컨택스트(Execution Context) 와 렉시컬 환경(Lexical Environment)
모던 자바스크립트 딥다이브에서는 다음과 같이 렉시컬 스코프를 정의합니다.
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프라 한다.
이해가 더 쉽게 코드로 한번 보겠습니다.
function outerFunction() {
let outerVariable = "I am from outer scope";
function innerFunction() {
console.log(outerVariable); // I am from outer scope
}
innerFunction();
}
outerFunction(); // I am from outer scope
innerFunction
은 outerFunction
의 내부에 정의되어 있습니다. innerFunction
은 outerFunction
의 outerVariable
에 접근할 수 있습니다. 이는 함수가 선언된 위치의 상위 스코프를 기억하고 그 스코프에 접근할 수 있기 때문입니다.
클로저는 함수가 자신이 생성된 렉시컬 스코프 외부에서 호출되어 실행될 때에도 그 스코프에 대한 참조를 유지하는 현상입니다.
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('Outer Variable: ' + outerVariable);
console.log('Inner Variable: ' + innerVariable);
};
}
const newFunction = outerFunction('outside');
newFunction('inside');
// Outer Variable: outside
// Inner Variable: inside
여기서 innerFunction
은 outerFunction
의 내부에 정의되어 있습니다.
innerFunction
은 자신이 생성된 스코프, 즉 outerFunction
의 스코프를 기억하고, outerFunction
의 호출이 완료된 이후에도 그 스코프에 접근할 수 있습니다. 그리고 이에 따라 innerFunction
은 outerVariable
에도 접근할 수 있습니다. 이것이 클로저가 동작하는 방식입니다.
클로저는 변수와 함수의 접근 범위를 제어하고, 특정 데이터와 상태를 유지하기 위해서 활용됩니다.
데이터 은닉: 클로저는 외부에서 접근할 수 없는 비공개 변수와 함수를 만들 수 있습니다. 이를 통해 데이터를 은닉하여 외부 접근을 막고 무결성을 유지할 수 있습니다. 예를 들어, React hook이나 순수 함수 등을 만들때에 사용됩니다.
비동기 작업: 클로저는 비동기 작업에서 이전의 실행 컨텍스트를 유지해야 할 때 유용합니다. 콜백 함수가 비동기적으로 실행될 때 클로저를 사용하면 함수 실행 시점의 변수를 참조할 수 있습니다.
function createLogger(name) {
return function() {
console.log(`Logger: ${name}`);
};
}
const logger = createLogger('MyApp');
setTimeout(logger, 1000); // 1초 후에 'Logger: MyApp' 출력
위의 예시처럼 클로저가 name 변수 "MyApp"을 저장하여 1초 후에 해당 값을 출력할 수 있습니다.
참고
함수형 프로그래밍으로 JavaScript의 잠재력을 최대한 활용하세요
자바스크립트 - 실행 컨택스트(Execution Context) 와 렉시컬 환경(Lexical Environment)
매일메일