본문 바로가기
javascript

[자바스크립트] 클로저(closure)

by jinbro 2017. 4. 11.

[목표]
- 이전에 배운 자바스크립트 관련 개념을 통해 클로저 이해하기
- 클로저 특징을 활용 예시 알아보기(클로저를 사용하는 이유)
- 클로저를 배움으로서 자바스크립트 개발에 있어서 도움이 되는지 스스로 생각해보기


[클로저란]
- 실행컨텍스트에 대한 지식이 있어야 이해할 수 있음
- 자바스크립트에서 함수는 일급객체(매개변수, 리턴값, 변수 대입 등 값처럼 사용가능)라는 것을 기억해야 이해할 수 있음
- 일급객체라는 말을 이해하려면 실행컨텍스트 포스팅 볼 것(자바스크립트 엔진이 함수 스코프 정의 시 객체 만들고 프로퍼티 - 값 정의하는 부분)
- 자바스크립트 뿐만 아니라 함수를 일급 객체로 취급하는 함수형 언어에서 사용되는 중요한 특성
- 외부함수와 내부함수와 관련된 것
1
2
3
4
5
6
7
8
function outer(){ // 외부함수
     function inner(){ // 내부함수 : 외부함수에 중첩되어진 함수를 가리킴
          var title = "hello"
          alert(title)
     }
     inner()
}
outer() // 알림창으로 hello가 찍힘
cs

- 내부함수는 외부함수의 지역변수를 참조할 수 있음 : 객체 내 객체(스코프 체인, 자바스크립트 객체)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function outer(){ // 외부함수
     var title = "hello"
 
     function inner(){
          /*
           내부함수 : 외부함수에 중첩되어진 함수를 가리킴
           var inner = function() { .... } 와도 같음, 변수 inner를 만들어 function 실행문을 할당하는 것
           보통 함수 안에서만 사용되는 함수를 정의할 때 사용함
          */
          alert(title)
     }
     inner()
}
outer() // 알림창으로 hello가 찍힘
 
cs

- 클로저란 외부함수의 실행이 모두 끝났음에도 불구하고 내부함수가 외부함수의 지역변수에 접근할 수 있는 것을 말함
1
2
3
4
5
6
7
8
9
10
function outer(){
     var title = "hello"
 
     return function(){ //함수를 리턴값으로 사용가능함(일급객체이기 때문에)
          alert(title)
     }
}
 
var inner = outer() //함수 실행으로 인해 반환되는 값이 inner 라는 변수에 저장됨(+ 일급객체 특성)
inner() // outer는 return 키워드를 통해 종료가 되었음에도 불구하고 inner() 를 호출하면 hello 표시된 알림창이 뜸
cs

- 내부함수가 소멸될 때(변수 초기화)까지 외부함수가 소멸되지 못하고 라이프사이클을 유지하는 것을 클로저라 함
- 실제로 외부함수의 실행컨텍스트는 스택에서 없어졌지만 객체가 살아있기때문에 값 참조가 가능한 것
  (이해 못하겠으면 실행컨텍스트 포스팅 참고)


[클로저 특징이 발생하는 이유]
- 클로저를 이해하려면 함수는 일급객체, 함수 호출 시 내부 처리를 알 수 있는 실행컨텍스트 지식이 있어야함
- 외부함수 return 키워드로 외부함수 실행컨텍스트가 스택에서 파기되었음에도 변수 객체(VO 혹은 활성객체라 함)는 살아있음
- 외부함수의 변수 및 함수 정보, 스코프 체인를 저장하고 있는 객체가 살아있어서 내부함수가 스코프 체인을 검색하고 값을 참조함
- 외부함수의 값 복사X 실제 값을 참조함(스코프 체인을 통해 검색 후 그대로 가져옴 : 객체 저장된 값을 그대로 참조한다는 뜻)
- 자바스크립트 엔진은 지역변수의 값을 바인딩하기위해 스코프체인 검색한다는 것 : 체인에 없을 경우 undefined를 리턴


[클로저를 사용할 때 조심해야할 것]
- 클로저를 무분별하게 사용할 경우, 자원을 마구잡이로 잡아먹을 수 있음(외부함수의 객체를 그대로 살려두는 것이기 때문에)
=> 그래서 아래 사용 예시를 체크하고 꼭 필요한 곳에 사용할 수 있도록 연습해야함
=> 우선 외부함수, 내부함수를 사용하는 이유를 알아야함, 지역변수(함수 내에서만 사용하는)를 사용하는 이유도 알 것
- 외부함수에서 정의된 지역변수나 외부함수의 매개변수로 받은 값이여야 클로저 적용이 됨

(1) 잘못 사용된 예와 고친 예 : i는 외부함수에서 정의된 것이 아닌 for문의 i임
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var arr = []
 
for(var i = 0; i < 5; i++){
  arr[i] = function(){
    return i;
    //i는 
  }
}
 
for(var index in arr) {
  console.log(arr[index]());
}
 
/* 
 - arr 배열의 아이템 5개에 function이 들어감
 - 함수가 종료된 후 호출을 했을 때 i가 당시 값을 참조하고 있기 때문에
   함수 호출을 예상을 했을 때에는 0, 1, 2, 3, 4가 리턴될 것이라 생각하겠지만
   i는 저장된 함수의 지역변수가 아닌 for문의 i이기 때문에 그대로 참조 못함
   당시의 i값을 참조하도록(실행컨텍스트 포스팅 참고)하기 위해 아래와 같이 바꿔야함
   
 - 클로저는 함수가 호출될 때 실행컨텍스트가 생성되어 객체를 만들고 객체 내에 함수의 정보를 담음
   함수정보에는 스코프 체인, 지역변수 / 함수 정보, this 바인딩(지역 - 전역 순서대로 참고하면서 this 값 바인딩)이 들어감
   아래 코드를 분석해보면 
   (1) for문이 0 ~ 4 총 5번 돈다
   (2) 선언된 배열의 아이템으로 외부function이 들어간다
   (3) 외부function이 배열 아이템으로 들어갈 때 외부function은 호출됨 ( 아래 코드가 복잡해보이지만 arr[i](i) 형태임 )
   (4) 호출될 때 당시 i값이 외부function의 매개변수의 인수로 들어가게되고 외부function이 return 키워드로 종료됨
   (5) 외부function이 종료될 때 내부function을 리턴하는데 이때 내부function이 호출되는 것이고(실행컨텍스트 생성) 
   (6) 내부function이 return 키워드로 종료될 때 외부함수의 id(인수)를 참조하게됨 
   (7) 클로저 완성! 내부function은 외부function의 id값을 그대로 참조하고 있음
   (정리) for문의 i를 외부function의 매개변수로 돌려 외부function의 값으로, 내부function은 외부function의 값을 참조
         외부function이 return 키워드로 종료되었다고하더라도 이전에 받은 매개변수의 인수를 참조하고 있음
         종료 당시 리턴하는 id값은 외부function의 id값이고 id값은 for문의 i값(id값과 i는 같지만 서로 상관은 없음, 호출할 때만 사용함)
         기억해야하는 것은 외부function의 지역변수를 내부function이 참조한다는 것임, 이를 변수에 저장해놓는 것이다.
         변수에 저장해두고 변수를 나중에 호출하면 그때 당시 참조한 값을 for~in문으로 호출한다는 것임
*/
 
 
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]());
}
cs

- 외부함수의 id값을 내부함수가 리턴하고 그것을 변수(배열)에 저장하는 것
- 호출됐을 당시에 값을 내부함수가 참조를 함 
- 가장 중요한 것은 클로저를 이용할 때 외부함수의 지역변수 값이여야함 : 외부함수 값을 참조하기때문에 외부함수 라이프사이클이 죽지않음)


[클로저 사용 예시]
- 흐름에 맞는 값(실행컨텍스트 이해), private화(외부/내부함수 호출될 때의 값), 변수 혼동 방지(다른 함수에서 이름이 같아....)

(1) 첫번째 예시 : 누적 클릭횟수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
     <head>
     </head>
     
     <body>
          <button type="button" onclick="myFunction()">Count!</button>
          <p id="demo">0</p>
 
          <script>
               var add = function(){
                    var counter = 0
                    
                    return function(){
                         return counter += 1
                    }
               }()
 
               // var add에는 외부함수 호출이.... 외부함수가 호출되면 return으로 내부함수가...
               // 내부함수의 return으로 외부함수의 counter + 1
               // 외부함수가 호출되는 시점부터 DOM으로 id demo를 가진 p태그 내용으로 counter 변수 값을 넣음 
 
               function myFunction(){
                    document.getElementById("demo").innerHTML = add()
               }
           </script>
     </body>
</html>
cs

- 리턴될 당시의 외부함수 지역변수 값을 변수에 담고(그대로 참조함, 값 그대로) 계속 내부함수를 호출하는 것
- 외부함수가 종료되더라도 값은 그대로 참조하고 있기때문에 누적횟수 구현을 할 수 있음
- 내부함수가 실행 시 외부함수의 값을 그대로 참조하는 이유를 알기위해서 실행컨텍스트 포스팅 참고할 것
  (함수 호출 시 스코프 체인, 변수객체, this바인딩 보기)
- 겉으로 보면 내부함수가 실행될 때 당시의 외부함수의 지역변수 값을 그대로 잡아두고 참조하는 것
- 클로저 특징이 없었다면 전역변수로 선언하던지 클릭할 때마다 지역변수가 초기화되어서 누적 기능을 못만듬
- 실제 html 내용에 반영되는 함수는 따로 만들어놓은 것도 참고하면 좋을 것

(2) 두번째 예시 : 영화 타이틀 저장하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function movie_container(title){
     var title = title
     
     return {
          get_title : function(){
               return title
          }
 
          set_title : function(change){
               title = change
          }
     }
}
 
hulk = movie_container("increadible hulk")
ironman = movie_container("ironman")
 
console.log(hulk.get_title) // increadible hulk 출력
console.log(ironman.get_title) // ironman 출력
 
ironman.set_title("ironman 3"
console.log(ironman.get_title) // ironman3 출력
cs

- 외부함수 내에서만 가지는 내부함수가 있을 때(여러개 일 때 위와 같이 객체를 리턴하고 객체 내 사용가능한 메소드 정의)
- 외부함수의 지역변수가 다른 전역변수와 구분짓기위해 : 잘못 참조하지않도록 하기위해 - 변수의 private 화(각각 객체가 각각의 값을 가짐)
- 리턴되는 각각의 객체에 정의된 title 값은 각각 다름, 외부함수는 함수역할을 하고 리턴될 때에는 객체로, 객체는 각각 다른 값을 가지고 있음
- 실제로는 하나의 변수인데, 각각 다른 값을 가짐(클로저의 특성으로 인해 객체지향 구현)
- 공통되는 값, 함수는 프로토타입 객체에 저장하고, 값을 받아 객체 각각 다르게 저장할 때는 클로저를 이용하여 변수 혼동 방지 및 private화 시키기

- 실행컨텍스트를 이해하고 있으면 좋음, 함수가 호출될 때 컨텍스트를 생성하고 참조할 값을 지정함 : 당시의 값을 객체에 저장(흐름에 맞는 값)


[클로저 참고자료]
(1) poiemaweb 자바스크립트 : http://poiemaweb.com/js-closure

(2) 생활코딩 자바스크립트 클로저 : https://opentutorials.org/course/743/6544



댓글