[JavaScript] 클로저

jhm·2022년 8월 9일
0
post-thumbnail

모던 자바스크립트 Deep Dive 내용을 참조하였습니다.

클로저

클로저는 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 이미 생명 주기가 끝난 외부 함수의 변수를 참조할 수 있는 중첩 함수를 말한다.

const outer = () => {
	const x = 10;
  
  	const inner = () => { console.log(++x); }
    return inner;
}

const innerFunc = outer();
innerFunc(); // 11
innerFunc(); // 12
innerFunc(); // 13

클로저와 렉시컬

위 예제의 경우 outer 함수는 중첩 함수 inner를 반환하고 생명 주기를 마감하고 실행 컨텍스트 스택에서 outer 함수의 실행 컨텍스트가 제거된다. 그러므로 outer 함수의 지역 변수 x 또한 생명 주기를 마감하여 더이상 접근할 수 없을 것 같지만 outer 함수가 반환한 중첩 함수 inner는 outer 함수의 지역 변수 x에 접근할 수 있다. 어떻게 이것이 가능한 것일까?

JavaScript의 함수는 자신의 내부 슬롯 [[Enviroment]]에 상위 스코프를 저장한다.
outer 함수의 생명 주기가 종료되면 실행 컨텍스트 스택에서는 outer 함수의 실행 컨텍스트가 제거되지만 outer 함수의 중첩 함수인 innner 함수의 [[Enviroment]] 내부 슬롯에 저장된 outer 함수의 렉시컬 환경은 제거되지 않기 때문에 중첩 함수 inner에서 outer 함수의 지역 변수 x에 접근할 수 있는 것이다.

클로저의 조건

JavaScript의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모두 클로저이지만 실제는 그렇지 않다. 클로저는 다음의 조건을 모두 충족해야 한다.

- 상위 스코프의 식별자를 참조해야 한다.

const outer = () => {
	const x = 10;
  
  	const inner = () => {
      	const z = 20;
      	// 상위 스코프의 식별자를 참조하지 않는다.
      	console.log(z);
    }
    
    // 상위 스코프의 식별자를 참조하지 않으므로 중첩 함수 inner는 클로저가 아니다.
    return inner;
} 

- 외부 함수보다 생명주기가 길어야 한다.(외부 함수로부터 반환되어야 한다.)

const outer = () => {
	const x = 10;
  
  	const inner = () => {
      	// 상위 스코프의 식별자를 참조한다.
      	console.log(x);
    }
    
    // 상위 스코프로부터 반환되지 않아 생명 주기가 외부 함수인 outer보다 짧아 중첩 함수 inner는 클로저가 아니다.
    inner();
} 

위의 조건을 모두 충족하는 상위 스코프의 식별자를 참조하고 외부 함수보다 더 오래 유지 되는 중첩 함수를 클로저라고 한다.

클로저의 활용

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
즉, 상태가 의도치 않게 변경되지 않도록 상태를 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

// 상태 변수
const num = 0;

// 상태 변경 함수
const increase = () => {
  	return ++num; 
};

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

위 예제의 경우 상태가 전역 변수를 통해 관리되고 있기 때문에 누구나 접근할 수 있고 변경할 수 있어 안전하지 않다.

그렇다면 상태를 상태 변경 함수만이 참조하고 변경할 수 있도록 상태를 상태 변경 함수의 지역 변수로 관리해보자

// 상태 변경 함수
const increase = () => {
  	// 상태 변수
	const num = 0;
  
  	return ++num; 
};

console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1

상태를 상태 변경 함수의 지역 변수로 관리하여 의도치 않은 상태 변경은 방지했다.
하지만 상태 변경 함수가 호출될 때마다 상태가 다시 선언되어 초기화되기 때문에 상태가 이전 상태를 유지하지 못한다.

이런 상황에서 의도치 않은 상태 변경을 방지하면서 상태가 이전 상태를 유지하기 위해 사용하는 것이 클로저이다.

// 상태 변경 함수
const increase = (() => {
  	// 상태 변수
	const num = 0;
  
  	// 클로저
  	return (() => {
      	return ++num;
    });
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

위 예제의 경우 즉시 실행 함수가 호출되어 반환한 클로저가 변수 increase에 할당된다.

이처럼 클로저는 상태를 은닉하여 의도치 않은 변경을 방지하고 특정 함수(클로저)에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.

클로저가 꼭 하나여야만 하는 것은 아니다.

const couter = (() => {
  	// 상태 변수
  	const num = 0;
  
  	// 클로저인 메서드를 갖는 객체를 반환한다.
  	// 객체 리터럴은 스코프를 생성하지 않으므로 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다. 따라서 즉시 실행 함수의 지역 변수인 num을 참조할 수 있다.
  	return {
      	increase() {
          	return ++num;
        },
      	decrease() {
          	return num > 0 ? --num : 0; 
    };
}());
profile
Front-End 개발 독학 중

0개의 댓글