JavaScript

클로저

클로저

클로저(closure)는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 가르킨다. 클로저는 자바스크립트를 이용한 고난이도의 테크닉을 구사하는데 필수적인 개념으로 활용된다.  

내부함수

자바스크립트는 함수 안에서 또 다른 함수를 선언할 수 있다. 아래의 예제를 보자. 결과는 경고창에 coding everybody가 출력될 것이다.

function outter(){
    function inner(){
		var title = 'coding everybody';	
		alert(title);
	}
	inner();
}
outter();

위의 예제에서 함수 outter의 내부에는 함수 inner가 정의 되어 있다. 함수 inner를 내부 함수라고 한다.

내부함수는 외부함수의 지역변수에 접근할 수 있다. 아래의 예제를 보자. 결과는 coding everybody이다.

function outter(){
    var title = 'coding everybody';  
    function inner(){        
    	alert(title);
	}
	inner();
}
outter();

위의 예제는 내부함수 inner에서 title을 호출(4행)했을 때 외부함수인 outter의 지역변수에 접근할 수 있음을 보여준다.

클로저

클로저(closure)는 내부함수와 밀접한 관계를 가지고 있는 주제다. 내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다. 이러한 메커니즘을 클로저라고 한다. 아래 예제는 이전의 예제를 조금 변형한 것이다. 결과는 경고창으로 coding everybody를 출력할 것이다.

function outter(){
    var title = 'coding everybody';  
    return function(){        
    	alert(title);
	}
}
inner = outter();
inner();

예제의 실행순서를 주의깊게 살펴보자. 7행에서 함수 outter를 호출하고 있다. 그 결과가 변수 inner에 담긴다. 그 결과는 이름이 없는 함수다. 실행이 8행으로 넘어오면 outter 함수는 실행이 끝났기 때문에 이 함수의 지역변수는 소멸되는 것이 자연스럽다. 하지만 8행에서 함수 inner를 실행했을 때 coding everybody가 출력된 것은 외부함수의 지역변수 title이 소멸되지 않았다는 것을 의미한다. 클로저란 내부함수가 외부함수의 지역변수에 접근 할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는 특성을 의미한다.

조금 더 복잡한 아래 예제를 살펴보자. 아래 예제는 클로저를 이용해서 영화의 제목을 저장하고 있는 객체를 정의하고 있다. 실행결과는 Ghost in the shell -> Matrix -> 공각기동대 -> Matrix 이다.

function factory_movie(title){
    return {
        get_title : function (){
			return title;
		},
		set_title : function(_title){
			title = _title
		}
	}
}
ghost = factory_movie('Ghost in the shell');
matrix = factory_movie('Matrix');

alert(ghost.get_title());
alert(matrix.get_title());

ghost.set_title('공각기동대');

alert(ghost.get_title());
alert(matrix.get_title());

위의 예제를 통해서 알 수 있는 것들을 정리해보면 아래와 같다.

1. 클로저는 객체의 메소드에서도 사용할 수 있다. 위의 예제는 함수의 리턴값으로 객체를 반환하고 있다. 이 객체는 메소드 get_title과 set_title을 가지고 있다. 이 메소드들은 외부함수인 factory_movie의 인자값으로 전달된 지역변수 title을 사용하고 있다.

2. 동일한 외부함수 안에서 만들어진 내부함수나 메소드는 외부함수의 지역변수를 공유한다. 17행에서 실행된 set_title은 외부함수 factory_movie의 지역변수 title의 값을 '공각기동대'로 변경했다. 19행에서 ghost.get_title();의 값이 '공각기동대'인 것은 set_title와 get_title 함수가 title의 값을 공유하고 있다는 의미다.

3. 그런데 똑같은 외부함수 factory_movie를 공유하고 있는 ghost와 matrix의 get_title의 결과는 서로 각각 다르다. 그것은 외부함수가 실행될 때마다 새로운 지역변수를 포함하는 클로저가 생성되기 때문에 ghost와 matrix는 서로 완전히 독립된 객체가 된다.

4. factory_movie의 지역변수 title은 2행에서 정의된 객체의 메소드에서만 접근 할 수 있는 값이다. 이 말은 title의 값을 읽고 수정 할 수 있는 것은 factory_movie 메소드를 통해서 만들어진 객체 뿐이라는 의미다. JavaScript는 기본적으로 Private한 속성을 지원하지 않는데, 클로저의 이러한 특성을 이용해서 Private한 속성을 사용할 수 있게된다.

참고 Private 속성은 객체의 외부에서는 접근 할 수 없는 외부에 감춰진 속성이나 메소드를 의미한다. 이를 통해서 객체의 내부에서만 사용해야 하는 값이 노출됨으로서 생길 수 있는 오류를 줄일 수 있다. 자바와 같은 언어에서는 이러한 특성을 언어 문법 차원에서 지원하고 있다.

아래의 예제는 클로저와 관련해서 자주 언급되는 예제다. 

var arr = []
for(var i = 0; i < 5; i++){
	arr[i] = function(){
		return i;
	}
}
for(var index in arr) {
	console.log(arr[index]());
}

함수가 함수 외부의 컨텍스트에 접근할 수 있을 것으로 기대하겠지만 위의 결과는 아래와 같다.

5
5
5
5
5

위의 코드는 아래와 같이 변경해야 한다.

var arr = []
for(var i = 0; i < 5; i++){
	arr[i] = function(id) {
		return function(){
			return id;
		}
	}(i);
}
for(var index in arr) {
	console.log(arr[index]());
}

결과는 아래와 같다.

0
1
2
3
4

클로저 참고

댓글

댓글 본문
작성자
비밀번호
  1. Juyeon Heo
    어렵네요 ㅠㅠㅠ 이해될때까지 열심히 복습해보겠습니다.....ㅠㅠㅠ
  2. 바토
    신기한 자바스크립트의 세계네요. 재밌어요!
  3. 삼산우성
    마지막 영상 내용은 어렵네요... 좀 더 공부하고 한번 더 복습해야 될 거 같습니다 ㅠㅠ
  4. 호두
    이해가 됐어요
  5. 치미
    와 마지막 영상 머리에 쥐나는 느낌이네요
  6. 감사합니다.
  7. 미완성
    20190109
  8. Minsu Yun
    저도 마지막 영상은 조금 어렵네요 ㅠ 여러번 봐도..ㅎㅎ
  9. 소라김
    마지막영상 다시보기 !
  10. 스탐
    감사합니다,
  11. dj_yang
    마지막 영상이 살짝 어렵긴 하네요.
  12. 코딩가즈아
    C, C++, Python 만 써봐서 그런지 참 재밌고 신기한 기능이네요 ㅎㅎ
  13. hyuna lee
    여러번 봤는데 마지막 영상은 좀 어려워요
  14. 대단히 감사합니다!
  15. 참고
    추가 실험
    var a = [];
    for (let i = 0; i < 3; i++) {
    a[i] = {
    myValue: i,
    myFunc: function() {return this.myValue;}
    };
    }

    i = 10;
    a[0].myFunc(); // 0

    a[0].myValue = 10;
    a[0].myFunc(); // 10
    대화보기
    • 참고
      var 과 let 의 차이
      값을 함수로 주었을 때의 효과..

      - 실험1 -
      var a = [];
      for (var i = 0; i < 3; i++) { // var로 선언
      a[i] = {
      myValue: i,
      myFunc: function() {return i;}
      };
      }
      console.log(a[0].myValue); // 0
      console.log(a[0].myFunc()); // 3
      i = 10;
      console.log(a[0].myValue); // 0
      console.log(a[0].myFunc()); // 10

      - 실험2 -
      var a = [];
      for (let i = 0; i < 3; i++) { // let 으로 선언
      a[i] = {
      myValue: i,
      myFunc: function() {return i;}
      };
      }
      console.log(a[0].myValue); // 0
      console.log(a[0].myFunc()); // 0
      i = 10;
      console.log(a[0].myValue); // 0
      console.log(a[0].myFunc()); // 0

      // myValue에 들어간 i 는 '값'만 남기고 사라지지만
      // myFunction에 들어있는 i 값는 "i"로서 남아있습니다.
      // i를 let으로 선언하게 되면 a[0], a[1], a[2] 각각에 들어간 "function { return i; }" 속
      // i 들은 각기 생성될 당시의 값(0,1,2)을 가지고 있는 것으로 생각됩니다.
      // let으로 선언되어있기 때문에 block-scope이므로 block 밖에서 변한 i =10;은 영향이 없구요..
      // var로 선언된 i는 function-scope이기 때문에 block 밖이라도 i값에 변화를 주게 되면 변하게 되구요..
      // 나름대로 이해하고 해석했는데 혹시 틀리면 답글에 말씀달아주세요~
      참고 : MDN문서 https://developer.mozilla.org......res
    • 김해중
      면접 준비때문에 보고 있는데 여전히 오리무중...ㅠㅠㅎ
    • egoing
      의견 감사합니다. 오래전에 만든 수업이라 기억이 분명치는 않습니다만, 저도 클로저에 대한 명확한 개념이 없이 공부를 하면서 수업을 만들었던 것 같습니다. 저도 다시보니까 횡설수설하는 것이 느껴지네요. 여전히 클로저를 명확히 이해하고 있다고 생각되진 않습니다만 점 더 잘 정돈된 수업을 다시 만들어보거나, 더 좋은 자료를 안내하는 방법으로 혼란스럽지 않게 하겠습니다. 의견 고맙습니다. 혼란스러워하는 학습자분들에게 친절하게 답변드리지 못한 것도 죄송스럽게 생각합니다.
      대화보기
      • heroyooi
        저도 똑같아요..
        이유가 궁금하네요..
        대화보기
        • 안장호
          자바에서 getter setter의 개념과 비슷한 것 같네요. 언어에서 표현하는 방식만 다를 뿐이네요.
          강의 감사합니다. ^^
        • 듀티프리
          좋아요. 감사합니다.
        • 지나가던컴공
          typeof 사용하실때 'String' 이 아니라 'string' 으로 해야하지 않나요?
          저같은 경우에는 'String' 으로 했을때 문자열을 전달 받아도 false 입니다.
          에디터 차이인지 궁금합니다.
        • 의문점박이
          --------------------------------------------------------
          var arr = []
          for(var i = 0; i < 5; i++){
          arr[i] = function(id) {
          return id
          }(i);
          }
          for(var index in arr) {
          console.log(arr[index]());
          }
          ------------------------------------------------------
          위와 같이 해도 값은 0,1,2,3,4가 나오는데 클로저랑 상관있는거 아니지 않나요?
        • 정지현
          어렵네요 ㅠ.ㅠ 강의 다 보고 자바 스크립트로 프로그래밍 하면서 다시 한 번 살펴보겠습니다^^
        • 컴공의자랑
          그 아래 alert 를 보시면, 각 변수에서 set_title 함수를 호출하고 있습니다.
          ghost, matrix 에는 get_title, set_title 함수가 들어있는 "객체" 가 저장됩니다.
          대화보기
          • 띠링
            감사합니다!
            대화보기
            • milhouse
              질문 있습니다!

              ghost = factory_movie('Ghost in the shell');
              matrix = factory_movie('Matrix');

              이렇게 하면 자동적으로 set_title 메소드가 실행되는건가요? set_title을 안해도 이미 설정이 되어있네요..
              왜그런걸까요?!
            • 참조의 성질을 응용한 것이네요 깔끔하게 이해하고 갑니다 감사합니다
            • 박인호
              12-14
              수강완료.
            • Jupi
              와... 친철한 설명 고맙습니다!!
              덕분에 이해 할 수 있었습니다. ^^
              대화보기
              • 홍승우
                for문에서 let를 쓰는것은 지역변수의 유효범위를 더욱더 축소 시키는겁니다.

                차근차근 공부해가는 입장에서 다른 방법들을 많이 생각해보는 것은 좋겠지만 제 경험상.. 공부 과정에서
                많이 햇갈리는 것들이 생길수 있으니까 강의 내용을 차분히 따라가보는게 좋을거 같습니다.
                대화보기
                • 홍승우
                  햇갈려하시는 분들이 많은데..
                  for(var i = 0; i < 5; i++){
                  arr[i] = function(){
                  return i;
                  }
                  }
                  이 코드는 arr배열에 익명함수의 설계도를 담은겁니다. 함수를 실행한게 아니라 함수의 설계도를 배열에 저장했다고 보시면 될거 같네요.
                  따라서 함수가 실행된건 아니니 각 배열에 담긴 것은 return될 i가 아니라 동작하기전 함수가 들어가 있는겁니다.
                  그 후에
                  for(var index in arr) {
                  console.log(arr[index]()); <-- 여기를 보시면 arr[index]가 아닌 arr[index]()죠. arr가 이미 i를 리턴 받은 상태라면 ()를 쓸 수 없겟죠. 여기서 함수가 실행되는 겁니다.
                  }
                  여기에서 루프를 통해 각 배열에 담겨있는 함수를 호출하게 되면 함수가 실행 후 return 값 i를 밷게 되는데
                  여기서 i는 이미 위에 있는 루프의 동작이 끝난 상태이므로 5가 되겠죠?
                  그러므로 5를 5번 리턴하게 되는겁니다.

                  여기서 알 수 있는 점은 top에서 루프가 돌 때 i의 값을 배열에 저장하는 것이 아니라 함수를 할당하는 것이기 때문에
                  우리는 처음 코드에서 5를 5번 반복하게 된다. 해결책은 처음 루프에서 함수를 즉시실행하면 되는 겁니다.
                  for(var i = 0; i < 5; i++){
                  arr[i] = function(){
                  return i;
                  }();
                  }
                  이렇게 하면 함수가 즉시 실행되서 arr에 담기는 값은 func이 아니라 return값 i겟죠?
                  console.log(arr[index]()); 이 루프 또 한 func이 아닌 int이므로 생성자를 빼주면 0,1,2,3,4를 볼수 있겠죠?

                  다만 이 강의에서는 클로저에 대해 설명하기 때문에 아래 코드와 같이 작성하여 결과를 도출하는 겁니다.
                • 고스트프리
                  // let을 적으니 되네요... 더 헤깔리기 시작하네요^^
                  var arr = [];
                  for(let i = 0; i<5; i++){
                  arr[i] = function(){
                  return i;
                  }
                  console.log(arr[i]);
                  }

                  for(var index in arr){
                  console.log(arr[index]());
                  }
                • 고스트프리
                  아!~ 저도 마지막이 이해가 안가네요.... 5가 5번찍히는데서 막혔네요... 일단 패스합니다.^^
                • 개발자가 되고싶은 고등학생
                  이게 왜 55555야???라는 분들이 많아서 한번 써봅니다.

                  var arr = []
                  for(var i = 0; i < 5; i++){
                  arr[i] = function(){
                  return i;
                  }
                  }
                  for(var index in arr) {
                  console.log(arr[index]());
                  }
                  ---------------------설명충↓----------------------------
                  arr[i]는 직접 i에 들어있는 값을 접근해서 이용한거고,return i는 i란 변수를 사용할거다!!!!라는 정의만 담겨있는겁니다. 왜나면 arr[i]는 지금 바로 사용하고,함수속 return i는 정의만 해놓은거니까요~
                  ->마지막에 i++이 되어 i가 5가 된 것은 이해하셨나요? 5는 return은 되지않았지만 5가되었다는 점!
                  그래서 for(var index in arr) 하면 5번 돌게되고, i는 5기 때문에 계속 5가 출력됩니다~~
                • 수복
                  우와... 클로저가 자바에서 캡슐화에 해당되는 부분인가요? 마지막 응용부분은 정말 어렵네요~
                • 이장원
                  마지막 4강 코드 바뀌기 전과 바뀐 후의 차이와 역할에 대해 조금 더 설명해주실 분 계신가요.. ㅠ
                  제가 약간 머리가 멍텅해서,,
                • GoldPenguin
                  완료했습니다.
                • GoldPenguin
                  완료했습니다.
                • 송성태
                  이건 댓글을 남길 수 밖에 없군요.

                  [5, 5, 5, 5, 5]가 나오는 이유.
                  for 문)
                  함수에 있는 변수가 외부 변수에 접근하지 못한다. 그래서 함수 자체가 5번 리턴되어 배열 arr에 저장된다. for 문의 반복이 끝날 때 변수 i는 5가 된다. 이 변수가 배열에 저장된 변수 i에 대입된다. ( i = 5)

                  for in문) 배열에 있는 함수를 index 순서로 호출하면 [5, 5, 5, 5, 5] 가 된다.

                  [0, 1, 2, 3, 4]가 나오게 하려면,
                  내부함수(클로저)를 외부 함수안에 넣어, 내부함수가 외부함수의 변수를 참조할 수 있도록 한다. 외부함수의 입력인자가 내부 함수의 return값이 되도록, 외부함수의 입력인자와 배열의 원소이름 변수를 다르게 설정한다.

                  말로 써 놓으니 더 어렵게 느껴네요 ㅠ.ㅠ. 여러분들 덕분에 잘 이해했습니다. 감사합니다!
                • sudar
                  마지막 예제에서 코드 수정전에 왜 undefined가 아니라 5가 출력되는지 잘 모르겠어요.. 혹시 return 이후에도 i는 전역변수라 값이 계속 바뀔수 있는건가요??
                  -----------------------------------------------------
                  실제로 arr[i]에 들어가는건 함수이고 함수에서 return 되는건 반복문이 돌아가는 순간의 i의 값이 아니라 그냥 i라는 변수자체가 들어가게되고 for문이 모두 돌아간 이후에 i는 5가 됩니다. (왜 5가 되냐면.. i가 4일때 ++되어 5가 되고 5가 되는 순간 for문을 돌리는 조건에 부적합하여 for문 안의 연산은 시행하지않고 종료 되는것이기에 5입니다.)
                  따라서 arr[0]~arr[4]가 반환하는것은 그냥 i라고 생각하시면 됩니다. 그런대 i가 마지막에 5로 초기화 되었으니
                  5가 나오는 것이지용.... 제가 이해한것이 맞나요..??ㅋㅋㅋ
                • keio
                  우와 도비도비님 해설 덕에 이해했어요. i를 통째로 넣는 생각은 못했네요.
                  for loop 이 돌면 i가 define이 안 되어있는데 어떻게 넣은거지 뭐지 5를 어떻게 넣었지 이랬는데
                  그냥 컴퓨터가 arr[0] 부터 arr[5]까지 i 라는 놈을 집어 넣었구나 라고 들으니 이해가 가요. 감사합니다!
                  대화보기
                  • Hyung Keun Kim
                    클로저 헷갈리네요 나중에 다시 한번 복습하러 올게요ㅠㅎㅎ
                  • 아사다마오리족
                    설명고맙습니당
                    대화보기
                    • Jeong Min Lee
                      좋은 강의 감사합니다.
                    • sunday00
                      i가 4일때 마지막으로 ++ 1을 더하고 끝나므로 5에서 끝납니다.
                      다음 루프에서 i값이 5보다 작지는 않으므로(같으므로) 실행되지 않구요.
                      대화보기
                      • 질문하기전에 아래 댓글을 보니 많이 이해가 됨
                        댓글중 1년전의 아래에 좋은 예제가 있음.
                      • ㅇㅇ
                        중괄호 뒤에 소괄호가 들어가는 이유가 뭐지 하고 끙끙대다가 이제 알았습니다.
                        function에서 중괄호까지가 익명함수였군요!
                      • bassplayerlee
                        안녕하세요, 이고잉님.
                        좋은 강의 잘 듣고 있습니다.
                        세번째 동영상 예제처럼 내부함수, 외부함수, 클로저를 사용하는 이유는 파이썬에서 지원하는 클래스와 인스턴스 개념을 자바스크립트에서 사용하기 위해서인가요?
                        질문 남겨봅니다.

                        뒷 강의를 듣다가 추가로 질문드릴게요.
                        3번째 동영상 예제는 실제로 사용하는건가요 아니면 예제로써 보여주신 건가요?
                        뒷 강의를 듣다보니 생성자와 new 라는 개념을 자바스크립트가 지원하는데 저렇게 코드를 짜는 이유가 있을까 싶어서 질문드립니다.
                      • Dong Il Kim
                        인자값(argument)는 함수에 넣어줄 값을 말합니다. input... 함수에 어떤 값을 넣고, 기대하는 값이 나오는 프로세스잖아요.

                        인자값 -> 함수 -> 값

                        함수에 들어가는게 인자값이고, 함수에서 나오는 결과값은 return 뒤에 나오는 값이라고 보면 됩니다.

                        function(인자값) {
                        연산...
                        return 결과값;
                        }
                        대화보기
                        버전 관리
                        egoing
                        현재 버전
                        선택 버전
                        graphittie 자세히 보기