[모던 자바스크립트 Deep Dive] - 24 클로저

Aneb·2022년 5월 10일
0
post-thumbnail
  • 클로저는 자바스크립트 고유의 개념이 아닌, 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
  • 따라서, 클로저의 정의는 ECMAScript 사양에 등장하지 않으며, MDN에서는 다음과 같이 정의하고 있다.
  • 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
const x=1;

function outerFunc(){
	const x= 10;

    function innerFunc(){
    	console.log(x); //10
    }

innerFunc();
}

outerFunc();

<예제 1 - innerFunc 함수가 outerFunc 함수의 내부에 정의된 중첩함수인 경우>

const x=1;

function outerFunc(){
	const x= 10;
	innerFunc();
}

function innerFunc(){
	console.log(x); //1
}

outerFunc();

<예제 2 - innerFunc 함수가 outerFunc 함수의 내부에 정의된 중첩함수가 아닌 경우>

예제1의 경우 innerFunc에서 outerFunc의 x변수에 접근 할 수 있으나 예제2에서는 접근할 수 없다. 이 같은 현상이 발생하는 이유는 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문이다.


24.1 렉시컬 스코프

  • 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프)라 한다.

13.5절에서 렉시컬 스코프에 대해 위와같이 정의한 바 있다. 이를 다시 아래와 같이 정의할 수 있다.

  • 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값. 즉 상위 스코프에 대한 참조함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다. 이것이 바로 렉시컬 스코프다.

24.2 함수 객체의 내부 슬롯 [[Environment]]

  • 함수는 자신의 내부 슬롯[[environment]]에 상위 스코프의 참조를 저장한다.
  • 이때 자신의 내부 슬롯에 저장된 상위 스코프의 참조는 현재 실행중인 실행 컨텍스트의 렉시컬 환경을 가리킨다.
  • 함수 객체는 내부 슬롯[[environment]]에 저장한 렉시컬 환경의 참조, 즉 상위스코프를 자신이 존재하는 한 기억한다.

24.3 클로저와 렉시컬 환경

const x =1;

//1.
function outer(){
	const x =10;
   const inner = function () {console.log(x);} // 2.
   return inner;
}

//outer 함수를 호출하면 중첩 함수 inner를 반환한다.
//그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); //3.
innerFunc(); // 4. 10

위 예제처럼 외부 함수(outer)보다 중첩 함수(inner)가 더 오래 유지되는 경우 중첩 함수는 이미 생명주기가 종료한 외부 함수의 변수(x)를 참조할 수 있다.
이러한 중첩 함수를 클로저라고 부른다.

  • 1-1. outer 함수가 평가되어 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시켤 환경, 즉 전역 렉시컬 환경을 outer 함수 객체의 내부 슬롯[[environment]]에 상위 스코프로서 저장한다.
  • 1-2. outer 함수를 호출 outer 함수의 렉시컬 환경이 생성되고 앞서 outer 함수 객체의 내부 슬롯[[environment]]에 저장된 전역 렉시컬 환경을 outer 함수 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 할당 한다.
  • 2. 중첩함수 inner 평가 됨 inner는 자신의 내부 슬롯[[environment]]에 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 outer 함수의 렉시컬 환경을 상위 스코프로서 저장한다.
  • 3.outer 함수의 실행이 종료 inner함수를 반환하면서 outer함수의 생명 주기가 종료된다. 하지만 이때 outer함수의 실행 컨텍스트는 실행 컨텍스트 스텍에서 제거되지만, 렉시컬 환경까지 소멸되는 것은 아니다.
    (inner함수의 내부 슬롯[[environment]]에 의해 참조되고 있기 때문에 가비지 컬렉션의 대상이 되지 않기 때문)
  • 4.outer 함수가 반환한 inner함수를 호출 inner함수의 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 푸시된다.

24.4 클로저의 활용

  • 클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다.
//카운트 상태 변경 함수
const increase = function (){
	// 카운트 상태 변수
	let num =0;

//카운트 상태를 1만큼 증가
rturn ++num;
};

//이전 상태를 유지하지 못한다.
console.log(increase()); //1
console.log(increase()); //1
console.log(increase()); //1
//카운트 상태 변경 함수
const increase = function (){
	// 카운트 상태 변수
	let num = 0;

// 클로저
	return function (){
		//카운트 상태를 1만큼 증가시킨다.
		rturn ++num;
	};
}();

console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3
  • 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고, 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용할 수 있다.

24.5 캡슐화와 정보 은닉

  • 캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다.

  • 캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라 한다.

  • 자바스크립트는 public, private, protected같은 접근 제한자를 제공하지 않는다.

  • 특정 패턴을 사용하면 정보 은닉이 가능한 것처럼 보이지만, 완전한 정보은닉을 지원하지는 못한다.

  • ES6의 Symbol또는 WeakMap을 사용하여 private한 프로퍼티를 흉내내기도 하였으나 근본적인 해결책이 되지는 못했다.

  • 다행히도, 2021년 1월기준 private필드를 정의할수 있는 새로운 표준 사양이 제안 되었으며 이에 대해서는 25.7.4절 private필드 정의 제안에서 살펴보도록 할 것이다.

25.6 자주 발생하는 실수

  • for문 사용시 var 키워드 대신 let을 사용하여 해결
profile
FE Developer

0개의 댓글