이 글은 클로저(closure)에 대한 제 나름대로의 이해를 풀어낸 글입니다. 혹시라도 제가 이해한 방식이 누군가에게 도움이 되길 바라면서 말이죠. 그 말인즉슨 클로저에 대한 저의 이해가 전부 다 틀리지는 않더라도 일부 잘못되었을 수도 있다는 뜻입니다. 따라서 클로저를 이해하실 때 클로저에 대해 설명하는 mdn 문서와 같은 공신력 있는 자료를 기초에 두시고, 아래의 내용을 참고해주시면 감사하겠습니다.
앞선 글에서 HTML의 태그들은 중첩이 가능하고, 이에 따라 족보를 형성한다고 말씀드린 적이 있습니다. 이에 따라 우리는 태그 간의 관계를 parent, child, sibling, descendant 등의 표현으로 이해하게 됩니다.
자바스크립트의 함수도 이처럼 중첩이 가능합니다. 어떤 함수가 그 내부에 또 다른 함수를 품고 내부함수를 리턴할 수 있다는 건데요. 예시를 한번 들어보겠습니다.
01 function titleOfBooks(title) {
02 return function () {
03 return title;
04 }
05 }
06
07 const code = titleOfBooks("codes");
08 typeof code; // "function"
09 code(); // "codes"
titleOfBooks 함수는 그 리턴 값으로 또 다른 함수를 내부에 품고 있습니다. titleOfBooks 함수에 전달인자로 "codes" 를 넣고 그 결과를 code 라는 변수에 할당하게 되면, 내부함수의 리턴값을 저장하기 때문에 code의 타입은 함수가 되죠.
즉, titleOfBooks 함수는 외부 함수이고, title을 리턴하는 2 번 줄의 함수는 내부 함수입니다. 이 때, 내부 함수와 내부 함수가 사용 가능한 범위까지를 클로저라고 이해할 수 있습니다.
일반적인 함수를 먼저 살펴보도록 하겠습니다.
01 function test() {
02 let x = 0;
03 return x;
04 }
05
06 console.log(x); // ReferrenceError: x is not defined
위의 예시에서 test 함수는 변수 x 를 선언하고 0 을 할당한 후 x 를 리턴하고 있습니다. 만약 함수의 바깥에서 6 번 줄처럼 x 값을 확인하려고 한다면 어떻게 될까요? 주석으로 적은 것처럼 레퍼런스에러를 띄울 겁니다. 왜냐하면 함수 안에서 선언된 x 는 지역변수이기 때문이죠. x는 함수가 실행될 때 생성되어 함수의 종료와 함께 사라집니다.
이것이 우리가 일반적으로 이해하고 있는 변수의 사용입니다. HTML 식으로 이해하자면 child(내부) 는 parent(외부) 의 변수를 활용할 수 있지만, parent(외부) 는 child(내부) 의 변수를 활용할 수 없는 것입니다.
다시 아까의 중첩된 함수로 돌아가보겠습니다.
01 function titleOfBooks(title) {
02 return function () {
03 return title;
04 }
05 }
06
07 const code = titleOfBooks("codes");
08 console.log(typeof(code)); // "function"
09 code(); // "codes"
변수의 사용에 대해 설명하면서 언급한 것처럼, 내부 함수는 외부 함수의 범위 안에 있는 변수를 사용할 수 있습니다. 1 번 줄의 title 은 titleOfBooks 의 매개변수이기 때문에, 내부 함수가 이를 사용할 수 있죠.
하지만 뭔가가 이상하지 않나요? 분명 외부 함수는 7 번 줄에서 종료가 되었습니다. 그 다음부터는 title 에 접근이 불가능해야 정상이죠. 그런데 9 번 줄에서 code() 는 7번 줄에서 입력한 전달인자인 "codes" 를 출력하고 있습니다.
이는 클로저 함수가 자기 자신과 자신이 사용 가능한 변수까지를 "폐쇄" 하기 때문입니다. 요즘 익숙한 개념인 '자가격리'라고도 할 수 있을 것 같은데요. 클로저 함수는 자기 자신과 자신이 사용 가능한 범위 내의 변수(외부 함수에서 선언하고 사용한 변수까지)까지를 closure 해서, 외부함수의 종료에도 불구하고 스스로가 사용 가능한 영역을 설정하게 되는 것이죠.
그래서 원래대로라면 함수의 종료 이후 사라졌어야 할 변수를 클로저 함수는 계속해서 활용할 수 있습니다. 만약 여러 개의 변수를 선언해 외부 함수에 각기 다른 전달인자를 주어 할당하게 된다면, 클로저 함수는 각각의 독립된 영역을 지닌 것처럼(실제로도 그런지는 모르지만) 작동합니다.
참고로 외부 함수 내부에 단일한 함수가 아닌 객체를 두고 객체 내부에 여러 개의 함수를 값으로 정의한 경우에도 클로저 함수로 동작합니다. 이 때 외부 함수를 변수에 할당한 경우, 메서드로 클로저 함수를 활용할 수 있습니다.
관련하여 생활코딩님의 클로저 강의를 링크로 남겨드립니다. 여러 개의 변수를 선언해 할당하는 것과, 객체를 할당한 클로저 함수를 클로저(3/4) 에서 확인하실 수 있습니다.