[JavaScript] Closure

전홍석·2025년 7월 13일

javascript

목록 보기
5/11
post-thumbnail

Closure

클로저는 반환된 내부함수가 자신이 선언됐을 때의 렉시컬 환경 (Lexical enviroment) 을 기억하여, 함수 외부에서 호출되더라도 그 환경 (스코프)에 접근할 수 있는 함수이다
즉, 클로저는 자신이 생성될 때의 렉시컬 환경을 기억하는 함수이다

function outerFunc() {
	var x = 10
    var innerFunc = function() {
   		console.log(x)
    }
    return innerFunc
}
var inner = outerFunc()
inner() // 10

// outerFunc는 실행이 끝났지만, 내부 함수인 innerFunc가 x를 참조하고 있어
// x는 GC(가비지 컬렉션) 에 의해 수거되지 않고 살아있으며, 이후에도 계속 접근할 수 있다
  • 외부함수가 이미 반환되었어도 외부함수 내의 변수는 이를 필요로 하는 내부함수가 하나 이상 존재하는 경우 계속 유지되는데, 이때 내부함수가 외부함수에 있는 변수의 복사복이 아니라 실제 변수에 접근한다는 것에 주의하여야 한다

상태 유지

클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다

<!DOCTYPE html>
<html>
<body>
  <button class="toggle">toggle</button>
  <div class="box" style="width: 100px; height: 100px background: red;"></div>

  <script>
    var box = document.querySelector('.box');
    var toggleBtn = document.querySelector('.toggle')

    var toggle = (function () {
      var isShow = false

      return function () {
        box.style.display = isShow ? 'block' : 'none'
        isShow = !isShow
      }
    })()

    toggleBtn.onclick = toggle
  </script>
</body>
</html>
  • 위에 코드에서 즉시실행함수 (IIFE) 는 함수를 반환하고 즉시 소멸하고, 이 함수가 반환한 내부 함수는 자신이 생성될 때의 렉시컬 환경에 속한 isShow 변수를 기억하는 클로저이다
  • 이 클로저는 .box 요소의 표시 상태를 제어하기 위해 isShow 값을 참조하며, 이 값은 클로저에 의해 계속 메모리에 남아 유지된다
  • toggleBtn.onClick = toggle 을 통해서 클로저를 이벤트 핸들러로 등록하고, 버튼을 클릭할 때마다 이 클로자가 호출되면서 isShow 값이 반전되고 상태가 유지된다

이처럼 클로저는 .box 요소의 표시 상태를 나타내는 isShow라는 변수를 안전하게 기억하고 제어할 수 있게 해준다

전역변수 억제

<!DOCTYPE html>
<html>
  <body>
  <p>클로저를 사용한 Counting</p>
  <button id="inclease">+</button>
  <p id="count">0</p>
  <script>
    var incleaseBtn = document.getElementById('inclease')
    var count = document.getElementById('count')
	
    // 1번
    var counter = 0
    function increase() {
      return ++counter
    }
    
    // 2번
    function increase() {
      var counter = 0
      return ++counter
    }
    
	// 3번
    var increase = (function () {
      var counter = 0
      return function () {
        return ++counter
      }
    }())

    incleaseBtn.onclick = function () {
      count.innerHTML = increase()
    }
  </script>
</body>
</html>
  • 1번 코드는 동작은 하지만 좋지 않은 방식이다.
    전역변수 counter는 누구나 접근하고 변경할 수 있으므로, 의도치 않은 상태 변경의 위험이 존재한다

  • 2번 코드는 counter를 함수 내부의 지역변수로 선언하여 전역변수의 위험을 피했지만, increase()가 호출될 때마다 counter가 0으로 초기화되기 때문에 상태를 기억하지 못한다.
    때문에 버튼을 눌러도 매번 1만 출력된다

  • 3번 코드는 즉시실행함수를 사용해서 클로저를 생성한 예로, 스크립트가 실행되면 즉시실행함수가 실행되어 내부 변수 counter와 그를 사용하는 함수를 반환하고, 이 반환된 함수는 increase 변수에 할당된다
    이 함수는 자신이 생성되었을 당시의 렉시컬 환경을 클로저로 기억하며, 이 안의 counter 변수는 외부에서 직접 접근할 수 없는 private 변수이기 때문에 의도되지 않은 변경에 대한 걱정이 없는 안정적인 프로그래밍이 가능하다

변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적인 원인이 될 수 있다. 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다

정보 은닉

function Counter() {
  var counter = 0

  this.increase = function () {
    return ++counter
  }

  this.decrease = function () {
    return --counter
  }
}

const counter = new Counter()

console.log(counter.increase()) // 1
console.log(counter.decrease()) // 0

생성자 함수 Counter는 increase, decrease 메소드를 갖는 인스턴스를 생성한다
이 메서드들은 자신이 생성될 당시의 렉시컬 환경인 Counter 함수의 스코프를 기억하는 클로저이다
두 메서드는 동일한 렉시컬 환경을 공유하므로, 하나의 counter 변수를 함께 참조하게 된다

Counter 함수 내부의 counter 는 this.counter 와 같은 프로퍼티가 아니라 지역변수이므로, 외부에서 직접 접근할 수 없다
하지만 increase, decrease 메서드는 클로저이기 때문에 자신이 기억하고 있는 렉시컬 환경을 통해 counter 변수에 접근할 수 있다

이처럼 클로저를 활용하면 JS에서도 private 키워드처럼 외부에서는 접근할 수 없는 내부 상태를 유지하는 방식, 즉 정보 은익을 흉내낼 수 있다.

참고
poiemaweb closure

profile
취뽀까지 숨참기

0개의 댓글