클로저

zimablue·2023년 5월 14일

javascript

목록 보기
10/30

함수가 생성될때 같이 발생하는 특별한 현상이며,
어떤 컨텍스트에서 선언한 변수를 참조하는 내부함수를 컨텍스트의 외부로 전달할 경우에 발생합니다.

이러한 특별한 현상은 함수(컨텍스트) 종료 후에도 사라지지 않는 지역변수를 만들 수 있습니다.



예시

const outer = () => {
  let a = 1;
  const inner = () => {
  	return ++a;
  };
  return inner;
};

const outer2 = outer();

console.log(outer2());
// 2
console.log(outer2());
// 3

호이스팅으로 보는 실행 순서 1

위 코드의 outer2 함수 호출전 환경을 생각해보면 호이스팅에 의해 outerouter2함수가 선언되었고 전역 실행컨텍스트의 environmentRecord 에는 함수가 닮긴 outer 와 undefined가 닮긴 outer2 함수가 있습니다.

const outer = () => {
  let a = 1;
  const inner = () => {
  	return ++a;
  };
  return inner;
};

const outer2;
// 위의 설명은 여기까지의 내용입니다.

outer2 = outer();

console.log(outer2());
console.log(outer2());

이미지 출처: 코어 자바스크립트


호이스팅으로 보는 실행 순서 2

outer 함수를 호출하면 outer 함수의 environmentRecord 에는 1이 닮긴 a 변수와 inner 함수가 있습니다.
outerEnvironmentReference는 outer 함수가 있습니다.
여전히 전역 실행컨텍스트의 environmentRecord의 outer2는 undefined입니다.

const outer = () => {
  let a = 1;
  const inner = () => {
  	return ++a;
  };
  // 위의 설명은 여기까지의 내용입니다.
  return inner;
};

const outer2;

outer2 = outer();
// outer()가 호출되어 위의 주석까지의 상황입니다.

console.log(outer2());
console.log(outer2());

이미지 출처: 코어 자바스크립트


호이스팅으로 보는 실행 순서 3

inner는 호출되지 않고 반환되었기 때문에 outer 함수의 실행은 마무리 됩니다.
전역 실행컨텍스트의 environmentRecord의 outer2에는 반환된 inner 함수가 닮깁니다.

일반적으로는 outer 함수의 실행이 마무리 되면서 outer의 실행컨텍스트도 종료가 되고, 함수 내부에서 선언되어 environmentRecord에 있는 지역변수 a는 가비지 컬렉터에 의해 사라져야 합니다.

하지만 outer2에 반환된 inner가 담기면서 inner는 언젠가 다시 호출될 수 있고 inner 내부에서 참조하고 있는 지역변수 a의 참조카운트는 0이 아닌 상태가 되어 사라지지 않습니다.

const outer = () => {
  // 변수 a는 inner가 불리게 되면 사용되어야 하기 때문에 사라지지 않음
  let a = 1;

  // 상수 outer2에 담기면서 inner가 언제든 다시 불릴 수 있게 됨
  const inner = () => {
  	return ++a;
  };
  return inner;
};

const outer2;

outer2 = outer();
// 위의 설명은 여기까지의 내용입니다.
// outer2 상수에 inner 함수가 반환됩니다.

console.log(outer2());
console.log(outer2());

이미지 출처: 코어 자바스크립트


호이스팅으로 보는 실행 순서 4

console.log()outer2를 호출하며 inner함수가 실행됩니다.
inner에는 변수 a가 없으므로 inner 컨텍스트의 outerEnvironmentReference를 통해 outer의 environmentRecord에 접근하여 살아있는 변수 a를 가져옵니다.

변수 a는 1이므로 ++된 2를 반환합니다.
inner 함수가 종료가 되며 inner 함수의 컨텍스트가 제거가 되어도 여전히 지역변수 a는 살아있습니다.

const outer = () => {
  let a = 1;
  
  const inner = () => {
  	return ++a;
  };
  return inner;
};

const outer2;

outer2 = outer();

console.log(outer2());
// 2
console.log(outer2());

이미지 출처: 코어 자바스크립트


const outer = () => {
  let a = 1;
  
  const inner = () => {
  	return ++a;
  };
  return inner;
};

const outer2;

outer2 = outer();

console.log(outer2());
// 2
console.log(outer2());
// 3

이미지 출처: 코어 자바스크립트

console.log()가 다시 한번 outer2를 호출하면 위의 설명과 동일하게 진행되며 2에서 1이 증가한 3을 반환합니다.

outer2inner 함수를, inner 함수가 지역변수 a를 참조하고 있기 때문에 a의 참조 카운트는 0이 되지 않고, 전역 컨텍스트가 종료 되면 그제서야 지역변수 a는 사라집니다.

게시글 처음의 설명과 같이 outer 함수의 컨텍스트에서 선언된 지역변수 a를 내부함수 inner에서 참조하고 있기 때문에 내부함수 inner가 외부로 전달되면서 outer함수가 종료된 이후에도 지역변수 a가 사라지지 않아 발생하는 특별한 현상입니다.



응용

클로저에 의해서 함수 종료 후에도 사라지지 않고 값을 유지하는 지역변수를 만들수 있을 뿐만 아니라 이를통해 외부로부터 내부 변수 보호할 수 있는 방법을 사용할 수 있습니다. (캡슐화)


user함수에 namestatus프로퍼티는 없지만 프로퍼티에 대한 getter와 setter로 해당 값을 불러오고 변경할 수 있습니다.

function user (_name) {
	let _logged = true;

 	return {
    	get name() {return _name},
      	set name(v) {_name = v},
		login() { _logged = true },
      	logout() { _logged = false },
      	get status() {
        	return _logged
          		? 'login'
          		: 'logout';
        },
    }
}

let zima = user('성율');

nameuser 함수에서 선언된 변수입니다.
nameuser함수의 실행컨텍스트가 종료되면 사라져야하지만, return에서 반환되는 함수가 해당 변수를 사용하고 있어서 사라지지 않습니다.

console.log(zima.name);
// '성율'

zima.name = 'ruel';
console.log(zima.name);
// 'ruel'

_logged변수도 name과 같이 함수가 종료되어도 사라지지 않습니다.

console.log(zima.status);
// 'login'

zima.logout();
console.log(zima.status);
// 'logout'

0개의 댓글