생활코딩

Coding Everybody

클로저

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

    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에서 종료되었음)
    }
  21. Amousk
    좋은 강의 감사합니다.
  22. 금도끼은도끼
    혹시 제설명듣고 오해할가 싶어서 몇자적는데.. 제설명은
    함수에서 i가 전역변수로 사용되는경우에 한 한것이지..let처럼 { }범위를 갖는것에는 해당하지않습니다..
    한가지 더알아야할점은
    함수안에서 var i 와 그냥 i 라고 선언됐다면 의미가 다릅니다.
    함수안의 var 1는 지역변수 이고 함수안의 그냥 i 는 전역변수입니다..

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

    꼭 var i 와 그냥 i 그리고 let i 이 3가지쓰임에 대해 검색을하시고 차이점을 알아보시기바랍니다..
  23. 간텔
    파라미터 명은 정하기 나름이니까요 ㅎㅎ
    대화보기
    • 짜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]);
                            }