말랑말랑 LAB

[JavaScript] 클로저 본문

JavaScript/JavaScript 스터디

[JavaScript] 클로저

쭈02 2019. 1. 5. 10:13

자바스크립트 클로저



o 클로저(Closure) 정의


" 자기 자신이 정의된 환경에서 함수 안에 있는 자유 변수의 식별자 결정을 실행한다. " (모던 자바스크립트 입문)


" 클로저는 자신이 생성될 때의 스코프에서 알 수 있었던 변수를 기억하는 함수다 " (함수형 자바스크립트 프로그래밍)


“A closure is the combination of a function and the lexical environment within which that function was declared.” (MDN)

클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

  • 클로저에 대한 여러 정의가 있는데, 공통적인 부분을 꺼내보면, 클로저는 생성될 때의 환경(스코프)과 관련이 있다는 것이다.

  • 다음과 같은 코드가 있다고 해보자.

    • 함수 outerFunc 내에서 내부함수 innerFunc가 선언되고 호출되었다.

    • 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다.

    • 함수 innerFunc가 함수 outerFunc의 내부에 선언되었기 때문이다.

즉, 함수 innerFunc가 선언(생성)될 때, 상위 스코프(렉시컬 스코프)는 함수 outerFunc가 되는데, 이를 기억하고 있다는 것 !!


    • 내부함수 innerFunc가 outerFunc의 변수 x에 접근하는 과정에서 상위 스코프에 접근할 수 있는 것은
      렉시컬 스코프의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색하였기에 가능한 것

      • 1) innerFunc 함수 스코프(함수 자신의 스코프를 가리키는 활성 객체) 내에서 변수 x를 검색 --> 검색 실패 !

      • 2) innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프(함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체)에서 변수 x를 검색 --> 검색 성공 !

  • 내부함수 innerFunc를 함수 outerFunc에서 호출하는 게 아니라 반환한다면?
    • 함수 outerFunc는 내부함수 innerFunc를 반환하고 소멸
    • 즉, 함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어
      변수 x에 접근할 수 없을 것 같은데, 위 코드의 실행 결과는 변수 x의 값인 10이다.
    • 이미 life-cycle이 종료되어 함수 outerFunc은 실행 컨텍스트 스택에서 제거되었지만, 지역변수 x에 접근하고 있다 !
    • 이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우,
      외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 함수를 클로저(Closure)라고 부른다.

(클로저를 다시 정의)

클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여

자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다.


## 아래의 코드를 실행시키면 어떤 결과가 나올지 예상을 먼저 해보고, 실행해보자 !



## 위 코드는 다음과 같은 과정으로 실행된다.



o 클로저의 활용

  • 클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억해야 하므로 메모리 차원에서 손해를 볼 수 있다.
    그렇다면, 왜 클로저를 사용할까?

  • 클로저가 유용하게 사용되는 상황에 대해 살펴보자 !

- 상태 유지
  • 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다. 아래 예제를 살펴보자.

 


① 즉시실행함수는 함수를 반환하고 즉시 소멸

  • 이 함수는 자신이 생성됐을 때의 렉시컬 환경(Lexical environment)에 속한 변수 isShow를 기억하는 클로저다.
  • isShow는 box 요소의 표시 상태를 나타낸다.

② 클로저를 이벤트 핸들러로서 이벤트 프로퍼티에 할당

  • 이벤트 프로퍼티에서 이벤트 핸들러인 클로저를 제거하지 않는 한 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않는다.
  • 다시 말해, 현재 상태를 기억한다.

③ 버튼을 클릭하면 이벤트 프로퍼티에 할당한 이벤트 핸들러인 클로저가 호출됨

  • 이때 .box 요소의 표시 상태를 나타내는 변수 isShow의 값이 변경된다.
  • 변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하며 자신의 변경된 최신 상태를 계속해서 유지한다.


  • 이처럼 클로저는 현재 상태(위 예제의 경우 .box 요소의 표시 상태를 나타내는 isShow 변수)를 기억하고 이 상태가 변경되어도 최신 상태를 유지해야 하는 상황에 매우 유용하다.
  • 만약 자바스크립트에 클로저라는 기능이 없다면 상태를 유지하기 위해 전역 변수를 사용할 수 밖에 없다.
  • 전역 변수는 언제든지 누구나 접근할 수 있고 변경할 수 있기 때문에 많은 부작용을 유발해 오류의 원인이 되므로 사용을 억제해야 한다.
- 전역 변수의 사용 억제
  • 버튼이 클릭될 때마다 클릭한 횟수가 누적되어 화면에 표시되는 카운터를 만들어보자. 이 예제의 클릭된 횟수가 바로 유지해야할 상태이다.
## 그런데, 아래 예제는 전역 변수 counter를 사용하여 카운팅하고 있다.. 이 전역변수는 언제든지 누구나 접근할 수 있고 변경할 수 있다.
만약 누군가에 의해 의도치 않게 전역 변수 counter의 값이 변경됐다면 이는 오류로 이어진다.
그렇다면, 이를 클로저를 사용한 것으로 변경해보자 !!
예제 파일 --> closure_ex2.html
정답 파일 --> closure_ex2_solution.html


- 정보의 은닉

  • 이번에는 생성자 함수 Counter를 생성하고 이를 통해 counter 객체를 만들어보자.

 

o 생성자 함수 Counter는 increase, decrease 메소드를 갖는 인스턴스를 생성

  • 이 메소드들은 모두 변수 counter를 기억하는 클로저이며 렉시컬 환경을 공유한다.
  • 생성자 함수가 생성한 객체의 메소드는 객체의 프로퍼티에만 접근할 수 있는 것이 아니며 자신이 기억하는 렉시컬 환경의 변수에도 접근할 수 있다.

o counter는 this에 바인딩된 프로퍼티가 아니라 변수

  • counter가 this에 바인딩된 프로퍼티라면,
    생성자 함수 Counter가 생성한 인스턴스를 통해
    외부에서 접근이 가능한 public 프로퍼티가 되지만,
    생성자 함수 Counter 내에 선언된 변수 counter는 Counter 외부에서 접근할 수 없다.
  • 하지만 생성자 함수 Counter가 생성한 인스턴스의 메소드인 increase, decrease는 클로저이기 때문에 변수 counter에 접근할 수 있다.
  • 이러한 클로저의 특징을 사용해 클래스 기반 언어의 private 키워드를 흉내낼 수 있다.






Comments