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. 홍주호
    20191026 완료
  2. 정홍
    완료
  3. 박창신
    완료
  4. 싸커홍
    나중에 다시한번 복습해야겠네요
  5. 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을 안해도 이미 설정이 되어있네요..
                        왜그런걸까요?!
                      버전 관리
                      egoing
                      현재 버전
                      선택 버전
                      graphittie 자세히 보기