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. 싸커홍
    나중에 다시한번 복습해야겠네요
  2. asdf
    var은 function scope이고
    let, const는 block scope 입니다.
    반대로 쓰셨네요
    대화보기
    • 스코프
      var의 scope가 function scope이고
      let, const가 block scope입니다.
      자바스크립트 원래 block scope가 없었는데 es6에서 let과 const가 생기면서 개념이 생긴것입니다.
      대화보기
      • 마지막 부분에서, scope 의 개념으로 접근했을 때,
        var 의 scope (block-scope)
        let 의 scope (function-scope) 서로 다른 특성으로 으로 구분되기 때문에,
        var 을 let으로 바꾸면 정상적인 출력(0 1 2 3 4) 를 얻을 수 있습니다.

        const arr = []
        for(let i = 0; i < 5; i++){
        arr[i] = function(){
        return i;
        }
        }
        for(let index in arr) {
        console.log(arr[index]());
        }
      • 박순기
        손흥민님 감사합니다... egoing님이 이렇게 설명했으면 대부분 사람들이 빨리 이해를 했을텐데..
        대화보기
        • 마지막 영상에서 잘못된 코드가 왜 5를 다섯 번 찍어내는지 정리해보았어요! 이해하는데 한참 걸렸는데
          잘못된 부분 있으면 알려주시면 감사하겠습니닷.
          --------------
          var arr = []
          for(var i = 0; i < 5; i++){
          arr[i] = function(){
          return i;
          }
          }
          for(var index in arr) {
          console.log(arr[index]());
          }
          --------------------
          - 위 코드에서 루프가 다 돌고 난후 `i++`이 실행되어 최종적으로 i값은 5이다
          - 첫번재 반복문에서 arr[index]에 담긴 것은 특정한 값이 아니라 함수이다.
          - console.log(arr[index]());를 호출했을 때, 외부의 컨텍스트에 접근할 수 있을 것으로 기대하였다.
          - 하지만 for문의 i는 외부함수의 변수가 아니었다. 훼이크!
          - 따라서 단순히 arr[index]에 담긴 함수가 5회 실행되어 현재의 i값인 5를 출력하였다.
        • 박지성
          아래에 이어서
          마지막 부분 해결부분을 설명해드리자면,

          var arr = []
          for(var i = 0; i < 5; i++){
          arr[i] = function(id) {
          return function(){
          return id;
          }
          }(i); // 이 부분에서 감싸주었던 함수가 "실행"이 됩니다.(not "참조"!) 때문에 i 값을 for문이 도는것과 같이 갖게되지요. 감싸주었던 함수의 매개변수(id)와 그 함수가 리턴한 함수가 클로저 관계를 맺게 되면서 그 값은 정해져버리게 됩니다. 그래서 마지막에 배열에 전달된 내부함수의 "참조"값이 "실행"되면서 값이 찍히게 되는겁니다.
          }
          for(var index in arr) {
          console.log(arr[index]()); // 실행 부분
          }

          // 이해가 잘 되지 않으시는 분들은 자바스크립트의 "스코프" 부분과 함수의 "일급객체" 부분을 공부하세요. :)
        • 손흥민
          마지막 부분은
          mdn 문서 를 참조하시면 이해하기 한결 수월하실겁니다.

          https://developer.mozilla.org......res
          (루프에서 클로저 생성하기: 일반적인 실수) 이 부분을 읽어보세요.

          생활코딩의 클로저 마지막 영상의 부분을 설명하자면,
          ( 반복문을 사용하며 5개의 클로저 환경이 생길 것 같지만 실은 모든 클로저가 그 환경을 공유하고 있기 때문에 모두 5 가 찍히는 것입니다.)

          여기서 말하는 "환경" 이란, 쉽게 말해 클로저를 사용해서 가져올 data를 말합니다.
          위의 예제에서는 outer함수의 지역변수나 factory_movie함수의 매개변수등이겠지용

          좀더 이해를 돕기위해
          위의 코드를 풀어서 쓰자면

          var arr = [];
          for(var i = 0; i < 5; i++){
          arr[i] = function(){
          return i;
          }
          }

          배열들은 함수들의 참조만 가지고 있을 뿐입니다.(실행되지 않습니다!)

          arr[0] = function(){
          return i;
          }
          arr[1] = function(){
          return i;
          }
          arr[2] = function(){
          return i;
          }
          arr[3] = function(){
          return i;
          }
          arr[4] = function(){
          return i;
          }

          i++

          실행부분은 여기가 되겠지요?

          console.log(arr[0]()); // i = 5
          console.log(arr[1]()); // i = 5
          console.log(arr[2]()); // i = 5
          console.log(arr[3]()); // i = 5
          console.log(arr[4]()); // i = 5
        • 마지막꺼 존나게 난해함 그냥
        • ㅊㅈㅅ
          마지막꺼 이해하는데 한오백년걸렸네요 와
          어떤순서대로 도는건지
          어떤게 먼저 실행되는건지
          왜 i번째 배열에 저장하는데 리턴하는 숫자는 5로 고정인지 고민이었는데

          function(){
          return i;
          };
          이상태 그대로 배열에 저장되고
          마지막에 리턴할때 이미 i 는 5까지 올라간 상태여서
          5
          5
          5
          5
          5
          입니다. 이해하시는데 도움되시길
        • 객체지향
          false니까 for문을 빠져나가니까 4까지만 실행되는거예요...
          대화보기
          • 너무어렵따..
            질문 있습니다! 이것저것 시도해보면서 코드를 수정해보았는데요.
            출력 값은 0,1,2,3,4가 나옵니다.

            var arr = []
            for(var i = 0; i < 5; i++){
            arr[i] = function(){
            return i;
            }(i); << 질문1. 내부함수에서는 외부함수에 있는 for 문을 가져다 쓸 수 없으니 이런 식으
            로 내부함수 밖에서 i를 출력하면 변수만 가져다 쓰는 것 이니까
            0,1,2,3,4가 되는 것 맞나요? (0,1,2,3,4가 나오는 이유가 이 것이 맞는
            지 궁금합니다.)
            }


            for(var index in arr)
            console.log(arr[index]); << 질문 2. (arr[index]);로 하면 출력이 정상적이게 되고 (arr[index]());로 하면 에
            러가 뜨는데 왜 그런건가요?ㅠㅠ
          • ㅇㅇ
            마지막 구문을 이해하려면 IIFE의 이해가 필수적임
            아리송하신 분은 꼭 검색해보세요!
          • 신입
            잘못된 예제에서
            다른거 다 이해했는데 왜 i가 4가 나와야하는지 이해를 못했는데 for문이 동작할때
            for(var i=0; i<5; i++) {
            //...
            }
            에서 i에 5가 들어가면 for문의 조건절이 false되면서 루프를 빠져나가고 i에는 최종적으로 5가 들어간다. 라고 이해했는데 제가 맞게 이해한건가요?
          • 초보자
            강의 이해하고 클로저식으로 코드 짜는데 1시간은 넘게 걸린거 같네요 감사합니다.
          • slowbegin
            저는 머리로는 이해가 되는데,
            코딩 따라하기를 해도 결과값도 제대로 안나오고
            어디서부터 잘못 타이핑한건지 찾으며 해매다가 시간이 가버리네요ㅠ
          • 궁금
            홍승우님의 답변 이해하는데 많은 도움이 됐어요!
            => arr 각 배열에 담긴 것은 return될 i가 아니라 동작하기전 함수 자체가 들어가 있는 것이기 때문에 나중에 배열에 각각 들어있던 해당 함수들이 선언될때 서야 i 값(5)를 가져온다.

            근데 또 궁금한점이..ㅠㅠ 해당 내용이 이전 유효범위 내용에서 배운 '함수 내 변수는 실행할때가 아닌, 선언할때의 값을 가져온다.'와 또 헷갈려요... 아래 코드에서 i 값이 10이 아닌 선언할때의 5가 오는 것 처럼요.. ㅠㅠ 혹시 설명가능하신분 부탁드립니다ㅠ

            var i = 5;

            function a(){
            var i = 10;
            b();
            }

            function b(){
            console.log(i);
            }

            a();
          • 그냥 _title은 이름 짓기 나름이라 신경안쓰셔도 될거 같네요 그냥 매개변수를 암거나 하나 받는다는 의미지 _title을 받아야 되는건 아니니깐. 예를 들자면 set_title : function(_korea){
            title = _korea
            } 해도 동작될거에요. 그냥 매개변수 하나를 받는다는거고 이름을 개판으로 지으면 안되니까 당연히 title로 사용할건데 좀 보안적이슈가 있을것같다는 암축된 의미로 _title로 개발자가 정한거고.. 어디서 _title 값을 받아오는게 아닌 아무 매개변수 하나를 받으면 그걸 _title로 쓰겠다는 의미같네요.
            대화보기
            • 컴공러
              흠.. 자바의 클래스와 비슷한 개념인건가요?
            • 세번째 강의에서 _title에 대한 변수는 별도로 선언을 하지 않았는데, 그걸 매개변수로 받는것이 가능한가요? title변수 같은 경우 외부 함수에서 매개변수로 받는다고하지만, _title과 title은 다른데, 어떻게 받을 수 있는건지 궁금해서 질문드립니다
            • Juyeon Heo
              어렵네요 ㅠㅠㅠ 이해될때까지 열심히 복습해보겠습니다.....ㅠㅠㅠ
            • 바토
              신기한 자바스크립트의 세계네요. 재밌어요!
            • 삼산우성
              마지막 영상 내용은 어렵네요... 좀 더 공부하고 한번 더 복습해야 될 거 같습니다 ㅠㅠ
            • 호두
              이해가 됐어요
            • 치미
              와 마지막 영상 머리에 쥐나는 느낌이네요
            • 감사합니다.
            • 미완성
              20190109
            • Minsu Yun
              저도 마지막 영상은 조금 어렵네요 ㅠ 여러번 봐도..ㅎㅎ
            • 소라김
              마지막영상 다시보기 !
            • 스탐
              감사합니다,
            • dj_yang
              마지막 영상이 살짝 어렵긴 하네요.
            • 코딩가즈아
              C, C++, Python 만 써봐서 그런지 참 재밌고 신기한 기능이네요 ㅎㅎ
            • hyuna lee
              여러번 봤는데 마지막 영상은 좀 어려워요
            • moon
              대단히 감사합니다!
            • 참고
              추가 실험
              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
                        와... 친철한 설명 고맙습니다!!
                        덕분에 이해 할 수 있었습니다. ^^
                        대화보기
                        버전 관리
                        egoing
                        현재 버전
                        선택 버전
                        graphittie 자세히 보기