클로저(closure)

클로저란?

사전적 의미

closure : 닫힘, 폐쇄, 완결성

MDN에서의 정의

: Lexical envirnment(내부 환경정보)와 내부함수와의 조합으로 함수가 생성될 때 매번 함께 발생한다.

즉, 함수의 생성과 함께 무조건 생기는 당연한 개념이다.

하지만 실제로는 클로저를 보편적인 상황에 모두 적용하지는 않고 클로저를 활용한, 클로저 환경에서만 발생하는 무언가 특별한 현상을 표현하기 위해 클로저라는 단어를 사용한다.

실행 컨텍스트의 내부 식별자 정보와 해당 실행 컨텍스트의 내부 함수에서의 외부 식별자 정보는 서로 관련이 있고 위에서 말한 이 둘사이의 조합은 이 두가지 뿐이다.

그래서 다시 정리하면 Closure는 실행 컨텍스트에서 선언한 변수를 내부함수에서 참조할 때 발생하는 특별한 현상이다.

특별한 현상이란?

var outer = function(){
	var a= 1;
	var inner = function(){
		console.log(++a);
	}
	inner();
}
outer();

여기서 outer()는 전역 컨텍스트의 내부 식별자 정보에서의 outer()값을 참조하고 a와 inner에 대한 내부 식별자 정보를 갖는 다

inner()는 outer()의 내부 식별자 정보에서의 inner()값을 참조하고 내부 식별자 정보는 없다.

여기까지는 특별한 현상이랄게 없다.

var outer = function(){
	var a= 1;
	var inner = function(){
		return ++a;
	}
	return inner;
}
var outer2 = outer();
console.log(outer2());
console.log(outer2());

inner()안에서 console.log를 하지 않고 ++areturn해주고 outer()안에서 inner를 실행하지 않고 return시켰다.

그리고 outer()의 결과를 outer2라는 변수에 담았다.

각 실행 컨텍스트를 살펴보자.

  1. 전역 실행 컨텍스트에 outer : f, outer2 : undefined 로 내부 식별자 정보가 정의된다.
  2. outer()의 실행 컨텍스트가 실행되어 진행되면서 outer() 실행 컨텍스트에는 외부 식별자 정보로 전역 컨택스트의 outer()값을 참조하고 내부 식별자 정보에 변수 a의 값이 1로 할당되고 innter()가 정의 된다. closure 작동 1
  3. outer()결과로 inner값이 반환되니까 전역 컨텍스트의 outer2: undefinedouter2: inner로 변경된다. closure 작동 2
  4. outer() 실행 컨텍스트는 종료가 되어 사라지지만 전역 컨텍스트에 outer2: inner가 정의되어 있어서 언제든 outer2로 인해서 inner()가 호출될 수 있기 때문에 inner내부에서 참조하고 있는 outer()컨텍스트 내부 식별자 정보에 기록된 a:1 이 죽지 않고 살아있다. 즉 outer() 실행 컨텍스트는 종료가 되었지만 outer의 내부 식별자 정보 중 a가 살아있다. 원래는 끝나서 사라지는 게 맞지만 참조 카운트가 0이라서 사라지지 못하고 살아있는 좀비 상태가 된 것이다.
  5. 이 상태에서 outer2()가 실행되면서 inner() 실행 컨텍스트가 생성된다.
  6. inner() 컨텍스트 내부에서 a에 접근을 하는데 이때 a는 종료가 된 outer()를 참조하는 외부 식별자 정보에 담겨있다. closure 작동 3 7.여기에 있는 a 즉 , 종료된 outer() 실행 컨텍스트의 내부 식별자 정보 a로 접근해서 a의 값을 변경하게 된다. 그리고 반환을 하게 되면 inner() 컨텍스트가 종료가 되고 console.log(outer2());2로 출력된다. 다시 또 console.log(outer2());이 실행되면서 inner()가 실행되고 a3이 되서 3이 출력이 되고 전역 컨텍스트가 종료되고 그때서야 outer()a도 사라진다.

closure 작동 순서

이러한 현상이 위에서 정의한 “컨텍스트에서 선언한 변수를 내부함수에서 참조할 경우에 발생하는 특별한 현상”에서의 특별한 현상이다.

클로저의 활용

클로저는 컨텍스트A에서 선언한 변수a를 참조하는 내부함수B를 컨텍스트A의 외부로 전달할 경우, 컨텍스트A가 종료된 이후에도 변수 a가 사라지지 않는 현상이다.

클로저를 사용하면 함수 종료 후에도 사라지지 않는 지역변수를 만들 수 있다.

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

user()_logged: true라는 값과 객체를 리턴하고 있다.

이 객체 안에는 user()의 매개변수인 _name을 받는 다. _nameuser()의 environment(내부 식별자 정보)에 정의되고 이 객체(user()return하는 객체)는 outerEnvironmentReference(외부 식별자 정보)로 _name을 참조하는 형태이다.

객체 안에서 get name()안에서 _name을 리턴하고 있기 때문에 클로저로 발생하여 user()실행 컨텍스트가 종료됨과 동시에 사라질 매개변수 _name이 리턴할 객체 안에서 또 리턴되면서 사용되고 있어서 참조카운트가 0이 안되니까 나중에 쓰일 변수라고 판단해서 계속 살아있게 된다.

_logged도 마찬가지이다.

그래서 laura.name 혹은 laura.status로 내부 변수에 접근할 수 있다.

name에 대한 gettersetter가 설정되어 있어서 name의 값을 변경시킬 수 있지만 statusgetter만 설정되어 있기 때문에 status의 값을 직접 바꾸지는 못한다.

ex) laura.name = ‘jjoo’; (ok) laura.status = false; (no)

Reference

Discussion and feedback