생활코딩

Coding Everybody

코스 전체목록

닫기

클로저

클로저

클로저(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. key5
    클로저 관련 다시 봐야할 것 같아요ㅠ 어렵네요...ㅠㅠ
  2. Windwalker
    마지막 예제.
    각 arr[i]함수에는 생성시 i값을 복제한 id가 하나씩 만들어지는군요
  3. 진진리
    2022.05.09
  4. 조은진
    자바를 먼저 접한 사람인데, 자바스크립트는 변수 타입이 몇개 안되서 private를 어떻게 표현할지 궁금했는데 이번 영상보고 신기했습니다. 직접 여러방식으로 사용해봐야지 체화되겠지만 너무 유익한 강의였어요!
  5. LLLEE
    fuction이 value로써 존재 가능하기에 argument로써 받을 수 있는 call back function이 되고
    argument 대신 함수를 통째로 넣어 내부함수, 외부함수가 되어 closer가 되는 것 이군요.
    결국 변수와 value를 할당시키는 기본 형태에서 value로 fuction을 대신하고 object를 대신해 closer가 되는 것이 네요. object 형태로 parmeter와 argument로 private하게 만들 수 있는 것 이네요.
    근데 마지막은 도저히 이해가 안되네요. 왜 저렇지?...
  6. 마스터하자
    클로저 마지막 응용파트부분 제일 어려운거같아요
    22.01.28 1차 완료 ,,,, 다시와서 2차 복습할 예정
  7. 낀찐
    2022. 01. 21 완료
  8. 칸타타
    너무 어렵다
  9. pmxsg
    2021.12.15. 수강
  10. 드림보이
    2021.12.08. 클로저 파트 수강완료
  11. GelandeWagen
    ok
  12. Grit
    감사합니다.
  13. seaWater
    2021. 9. 28. 완료
  14. 엘리
    어렵네요...ㅠ
  15. choi
    완료
  16. labis98
    20210821 good~~~!!!
  17. 평강의바다
    이렇게 주석을 달면 얼추 이해한건가요?? 잘못 이해한 부분 있으면 알려주세요 ~

    var arr = []
    for (var i = 0; i < 5; i++) {
    arr[i] = function (id) { //2. 매개변수 id에 인자값 전역변수 i들어옴
    return function () {
    return id;//5. 내부함수 호출되면서 지역변수 id값 반환
    }; //=> 3. 배열이 내부함수정의를 가리킴
    }(i); //=> 1. 외부함수 호출하면서 전연변수 인자전달
    }

    for (var index in arr) {
    console.log(arr[index]()); //4. 배열에서 내부함수 호출(외부함수는 1에서 종료되었음)
    }
  18. Amousk
    좋은 강의 감사합니다.
  19. 금도끼은도끼
    혹시 제설명듣고 오해할가 싶어서 몇자적는데.. 제설명은
    함수에서 i가 전역변수로 사용되는경우에 한 한것이지..let처럼 { }범위를 갖는것에는 해당하지않습니다..
    한가지 더알아야할점은
    함수안에서 var i 와 그냥 i 라고 선언됐다면 의미가 다릅니다.
    함수안의 var 1는 지역변수 이고 함수안의 그냥 i 는 전역변수입니다..

    위예문은 어떻게보면 함수안의 i가 전역변수가 되지않게 지역변수로 설정하기위한 편법으로 i를 인자로 전달하여 지역변수화 한것이라고 보면되죠

    꼭 var i 와 그냥 i 그리고 let i 이 3가지쓰임에 대해 검색을하시고 차이점을 알아보시기바랍니다..
  20. 간텔
    파라미터 명은 정하기 나름이니까요 ㅎㅎ
    대화보기
    • 짜rr
      다봤습니다. 감사합니다.
    • 금도끼은도끼
      네 중요한건 외부 i 값을 내부함수에서 쓴다는것이 중요하지 id하구는 상관없어요..
      위에 내용 id를 i로 바꿔서 실행해보시면 정상적으로 실행되는걸아실거에요..

      내부함수에서 리턴값으로 i를 쓴다는게 중요하지 이름이 id를 쓰든 i를 쓰든 상관이없어요..
      대화보기
      • 간텔
        함수를 실행시킬때 파라미터를 i로 주고
        함수에선 i를 id로 받아서 실행하는게 아닌가요?
        대화보기
        • 금도끼은도끼
          네 안그러면 함수안의지역변수값은 함수종료와함께 사라지기때문에 그값을 보존하기위해 클로저이란개념이 생긴거죠 ( 좀더명확하게하기 위해 id를 i로 바꿔도 같은결과나오죠 )
          좀어려워하실거 같은데 아주~~~~~~~~~~~~~~~~~~~중요한 개념이 하나가더있어요..꼭아셔야할개념인데..

          꼭 구글검색하셔서..
          value 와 Reference 개념에 대해 꼭 숙지하세요 아주중요하답니다 이게 이해안되면 함수나 여러부분에서
          나중에 진도가 나가더라도 막히게 됩니다..

          나름 설명잘해놓은거같아서 그곳 링크주소를 남깁니다..이것말구 더검색해서 찾아 읽어보세요..
          https://chati.tistory.com/150
          대화보기
          • 간텔
            외부함수에서 호출당시 들어온 값을 사용하기 위해선 내부함수 안에 변수를 만들어 그 변수에 들어온 값을 할당해준 다음 사용하라는 말씀이시죠?
            대화보기
            • 금도끼은도끼
              클로저는 아주 중요한 개념입니다... 클로저를 어디다쓸가요??

              이런함수특성때문에 이전값을 기억해야하는경우
              예를 들어서 전자시계 00: 00:00 시계에서 시간을 기억했다가 시 분 초가 올라가는것
              그리고 게임에서 카운트가 올라가는경우등 많이 쓰이죠...

              참좋은 예제입니다..클로저는 함수에서 기존값을 잊지않고 기억하기위해서 만들어진 개념이죠..
              대화보기
              • 앙냥냥
                친절한 설명 감사합니다 ~ ^ㅇ^ 아직 100% 이해했다고 할 수는 없지만 덕분에 어려웠던 부분은 해결했어요!!
                대화보기
                • 금도끼은도끼
                  함수는 함수내부에 변수(지역변수)가있으면 그걸사용하구
                  없다면 외부에서 가장 최근 값을 사용합니다(이전 변수 값은 어디에도 저장되지 않습니다.)

                  즉 arr[ i ] 의 i값과 function() { return i} 에서의 i값은 다른곳을 가르키고있는것이죠...

                  함수특징인데 함수에서 i값은 먼저 함수내부에 i가있나 확인하구 있으면 그걸쓰고
                  없으면 외부에서 찾는데 최종i값을 가져오게 되는겁니다..
                  예를 들면 for문밖에다가 i =10;이라고 추가해보자.. 10이 5번반복될것이다..
                  var arr = []
                  for(var i = 0; i < 5; i++){
                  arr[i] = function(){
                  return i;
                  }
                  }
                  i =10; <--------------이렇게 넣어주면 함수i는 가장최신값 10을 가져옴 최종적으로 10을 5번반복함.
                  for(var index in arr) {
                  console.log(arr[index]());
                  }
                  따라서 제대로 나오게 할려면
                  함수에 인자로 i값을 넣어주고
                  인자 i값을 함수내부에 존재하는 지역변수처럼 사용할수있도록
                  내부 함수를 만들어 리턴값으로참조하면된다..(말로 설명하려니 어렵죠 ^^;)
                  예를들어)
                  arr[i] = function(id) { <----------2번 : id 에 인자값 i가 들어 옴
                  return function(){
                  return id; <------3번: 들어온 인자값을 함수내의 지역변수처럼사용할수있게 내부함수를 만들어
                  } ----------------------리턴값으로 들어온 인자값을 가르킨다..
                  }(i); <------1번: i 값을 인자로 넣어주고..
                • 노원신
                  아직 초보라서 그런가 이해가 안되네요. ㅠㅠ
                • devHenry
                  var와 let의 차이점을 공부해보시면 될듯합니다
                  대화보기
                  • 이동훈
                    클로저 4/4 : 아래 코드가 잘 작동하는데...

                    let arr = [];
                    for (let i=0; i<5; i++) {
                    arr[i] = function () {
                    return i;
                    }
                    }
                    for (let index in arr) {
                    console.log(arr[index]());
                    }
                    실행결과 : 0 1 2 3 4
                  • hanel_
                    감사합니다^^
                  • hanel_
                    21.2.12
                  • 주니어개발자
                    클로저 넌 이제 내것이다.

                    애초부터 최신 ecma의 let const 등을 사용해 block level scope 을 활용하고
                    지역변수를 선언해 구분해 쓰면 깔끔하겠지만
                    실제로 유지보수 업무를 하게 되면 예전 코드들은 var 키워드만 사용해
                    전통적인 유효범위인 global scope와 function level scope만 두고 짜놓은 코드들이 많아서
                    그것을 맘먹고 최신방식의 코드로 수정할 때도, 또는 부분적으로 수정할 경우에도 반드시 이해하고 활용할 수 있어야 할 개념
                  • 강승
                    감사합니다.
                  • 박병진
                    2020.10.24 완료
                  • 박병진
                    2020.10.24 완료
                  • 20201012 완료

                    4번 영상 다시 참고하기
                  • Yongbeom Kwon
                    완료
                  • 물달
                    준바이 라는 분 덕분에 4번째 영상에 대한 이해와 let에 대해서 알아 볼 수 있었네요 설명해주셔서 감사합니다. 가능하다면 문서에도 업데이트가 된다면 좋을 것 같네요
                  • 김재원123
                    감사합니다. 경우의 수를 따져보는것을 생각하지 못하였습니다. 감사합니다 좋은하루 보내세요 ^^
                    대화보기
                    • dasjnfal
                      그렇죠, 귤님의 방식으로도 결과는 충분히 얻을 수 있지만 내부함수가 외부함수의 지역변수를 사용할 수 있다는 것은 알지 못하겠죠. 하지만 egoing의 예제에서는 내부함수에서의 id가 외부함수의 매개변수인 id를 공유하고 있다는 것을 알 수 있어요.
                      대화보기
                      • dasjnfal
                        변수이름이 중복이라서 나는 오류인 것 같습니다. title은 factory_movie의 argument로 선언이 되어 있는 상태 입니다. get_title은 그 scope 내에 있구요. 그래서 _title은 다른 변수의 이름으로서 활용이 가능하지만 title은 오류가 날 것입니다. 어떤 오류가 나는지 정확하게 말씀해주시면 더 좋을 것 같아요!
                        대화보기
                        • 김동호
                          생활코딩 자료가 2014년 이후로 변경되지 않아서 그런데 2015년 이후로 let과 const를 사용하면 위와 같은 문제가 발생하지 않습니다!
                          대화보기
                          • 준식
                            20200606 진행중
                            함수부터 어려워요~
                            한번 듣고 또 들읍시다~
                          • dhemdas
                            function factory_movie(title){
                            return {
                            get_title : function (){
                            return title;
                            },
                            set_title : function(_title){
                            title = _title
                            }
                            }
                            }



                            이부분에서 첫번째 객체 get_title 함수에 매개변수를 설정을 하면, 예를 들어 function(title){return title} 이렇게 설정을 하면 왜 오류가 날까요ㅜㅜ 매개변수를 설정하고 안 하고의 기준이나 차이를 모르겠어요. 밑에 set_title은 타이틀을 변경하기 위해 _ title이라는 아무 매개변수를 먹여준다고 이해해 봐도 어렵네요 고수님들 부탁드립니다
                          • 궁금한것이 있습니다!
                            0~4의 결과가 나올수 있게끔 혼자서 코드 연습을 해보다가 다음과 같이 작성하여도
                            똑같은 결과가 나온다는 것을 알았습니다.
                            제가 작성한 코드에서는 inner function을 사용하지 않았지만,
                            arr[i]가 함수가 아닌 결과값인 id만 저장하는 것처럼 보입니다.(즉, arr[i] = id)
                            이렇게 작성하여도 위에 있는 수업내용과 같다고 할 수 있는 건가요?
                            아니면 수업에서는 closure의 응용을 보여주기 위해 일부러 inner function을 사용한 건가요?

                            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]);
                            }
                          • 아기별
                            클로저가 지금까지 들었던 것 중에 제일 어려운 것 같아요..!
                            지금까지 여러 번 강의를 들어보고 댓글을 읽어보며 알게 된 점을 정리해보려고 합니다.
                            설명해주신 모든 분들, 이고잉쌤 정말 감사합니다! 혹시라도 틀린 부분이 있다면 알려주세요.

                            -------------------------------------------------------------------------------------------------------
                            //1. 결과값이 5가 다섯 번 뜬 이유
                            코드대로라면 결과값으로
                            0 1 2 3 4가 뜰 것 같은데
                            실제론 5가 5번 뜬다.

                            arr[i]는
                            function(){return i;}를 의미하고
                            그 함수에 들어있는 변수 i는
                            for 반복문이 다 돌고 나서 5로 정의된
                            전역변수 i를 데리고 온 것이라서 이런 현상이 생긴 것이다.

                            (흠..아무래도 return i 가 다섯 번 나열되는게 더 먼저고
                            그 뒤 i에 들어갈 값을 자바스크립트가 찾는데 그것이
                            for 반복문을 끝낸 var i의 값이 5라서 그 값을 집어넣어 그런 것이지 않을까 싶다)


                            //2. 처음 예상한 것처럼 결과값을 만들고 싶다면 : 클로저를 이용한다.
                            i가 arr[i]에 들어가서 arr[0~5]를 만들되
                            그 안에서 실행되는 함수는 for문에서 정의된 i=0; i<5; i++의 영향을 받지 않으려면
                            외부함수를 만들어 외부함수의 지역변수를 따로 정의하고,
                            내부함수의 변수가 for문을 도는 전역변수 대신 지역변수값을 끌어다 쓰게 해야 한다.

                            지금의 i가 외부변수가 아닌 이유는
                            arr[i]를 감싸고 있는 외부함수가 없기 때문이다.
                            따라서 익명 함수로 arr[i]=function(){return i}함수를 감싸줘서 외부함수의 역할을 하게끔 한다.


                            //3. 클로저 하기 위해 외부함수의 지역변수 따로 설정하기
                            하지만 여기에서 문제가 있다.
                            arr[i] = function(){
                            function(){
                            return i;
                            }
                            }();
                            }

                            외부함수를 따로 만들었다고 해도 이대로라면
                            for문의 i대신 따로 정의 내린 외부함수의 지역변수가 없으니
                            내부함수의 변수 i는 계속 for문에 정의되어 있는 전역변수 i값을 끌어다 쓸 것이다.

                            따라서 외부함수의 매개변수를 id로 바꿔주며 새롭게 지역변수를 정의한다.
                            그리고 내부함수의 변수를 외부함수의 지역변수를 따르게 하기 위해 i에서 id로 바꿔준다.


                            //4. 함수를 실행하면 생기는 일
                            이제 외부함수의 함수인자에 i를 집어넣어도
                            전역변수 i가 아니라 외부함수의 지역변수 id의 값이 되므로
                            i가 for반복문을 돌지 않게 된다.

                            그리고 내부함수의 변수는 외부함수의 지역변수를 끌어 쓰게 되어
                            for문을 돌지 않은 i값을 내보내게 된다.
                            따라서 결과값이 0 1 2 3 4가 나온다.


                            //정리를 하면
                            처음에 : 내부함수의 변수가 전역변수를 참조하던 현상이 있었다. (내가 원하던 건 이게 아니다!)

                            그러나 : 클로저(외부함수의 변수를 내부함수가 끌어다 쓸 수 있다) 덕분에 이런 현상을 막을 수 있다.

                            어떻게 하냐면 :
                            외부함수의 지역변수를 따로 정의한 뒤 내부함수의 변수와 이어줌으로써
                            전역변수와 내부함수의 변수와의 연결고리를 끊어주고
                            내부함수의 변수가 외부함수의 지역변수를 참조하게 한다.
                          • ironia
                            감사합니다. 이 부분을 더 공부해야겠군요~^^
                          • 브이에스코드
                            두 분 답변 감사합니다~
                          버전 관리
                          egoing
                          현재 버전
                          선택 버전
                          graphittie 자세히 보기