[FE] JS-Closure

Seongjun Lee·2022년 6월 29일
0

FE

목록 보기
8/12

클로져란?

클로져는 내부함수가 외부함수의 실행 컨텍스트에 접근할 수 있는 것을 가르킨다.

클로져는 함수와 함수에 의해 생성되는 범위 객체를 함께 지칭하는 용어.

function makeAdder(a) {
  return function (b) {
    return a + b
  }
}
var add5 = makeAdder(5)
var add20 = makeAdder(20)
add5(6) // 11
add20(7) // 27

자바스크립트는 함수가 실행될 때 언제나, '범위' 객체(실행 컨텍스트)가 생성되어 해당 함수내에서 생성된 지역 변수를 여기에 저장한다.

함수 매개변수로서 넘겨진 어떤 변수라도 여기에 초기값으로 저장하고 이것은 모든 전역변수와 함수가 들어있는 전역 객체와 비슷하지만 차이점이 있다.

  1. 함수가 실행될 때마다 새로운 범위 객체가 생성된다는 점.
  2. 전역 객체와 달리 범위객체는 JavaScript 코드에서 직접적으로 액세스할 수 없다는 점.
  • 현재 범위 객체의 속성에 반복 접근할 수 있는 수단이 없음.

따라서 함수가 호출되면, 범위 객체는 호출된 함수의 내용을 속성으로 가진 상태로 생성된다. 일반적으로 JavaScript의 가비지 컬렉터가 생성된 범위 객체를 제거해야하지만, 리턴된 함수가 여전히 범위 객체를 참조하고 있는 상태에서는 제거하지 않는다.

범위 객체는 JavaScript 객체 체계에서 사용되는 prototype 사슬과 비슷한 범위 사슬이라고 불리는 사슬을 형성한다.

메모리 누출

클로져의 부작용은 Internet Explorer에서 발생하곤한다. 가바지 컬렉터는 객체가 생성됨에 따라서 메모리가 할당되고, 사용하고난 메모리는 더 참조하는 다른 객체가 없을 때 되돌아가는 방식으로 동작한다.

브라우저 호스트는 HTML 페이지에 DOM 객체로서 표현되어있는 많은 수의 객체를 다뤄야한다. 이 객체들을 어떻게 할당하고 다시 거둬들일지는 브라우저 책임이다.

Internet Explorer는 이를 위해 자신만의 고유한, JavaScript와는 다른 가비지 컬렉션 방식을 사용하는데 이 과정에서 메모리 누출이 발생 할 수 있다.

IE에서 메모리 누출은 JavaScript 객체와 교유 객체간의 참조하는 중 자기 자신을 참조(순환참조)하게 되는 일이 발생할 경우라면 언제든지 발생하게 된다.

function leakMemory() {
  var el = document.getElementById('el')
  var o = { el }
  el.o = o
}

위 코드는 순환 참조로서 메로 누출을 일으킨다. IE는 완전히 다시 시작되기 전가지는 elo에 의해 사용되는 메모리를 반환하지 못한다.

메모리 누출은 오랫동안 실행되거나 큰 데이터 구조나 반복, 순환에 의해 누출되는 메모리 양이 많은 경우에서 실질적으로 고려할만한 가치가 생긴다.

누출을 일으키는 데이터 구조는 수차례에 걸친 참조 구조를 가지고 있어서 순환참조를 하고 있는지 명확하지 않은 경우가 더 많다.

클로져는 그렇게 되도록 하지않아도 간단하게 메모리 누출을 일으킬 수 있다.

function addHandler() {
  var el = document.getElementById('el')
  el.onclick = function () {
    this.style.backgroundColor = 'red'
  }
}

위 코드는 클릭시 배경색을 빨강으로 바꾸는 엘리멘트를 설정한다. 그리고 메모리 누출도 일으킨다.

그 이유는 el을 참조하면 의도와는 달리 익명 내부 함수 때문에 생성된 클로져 내에 붙잡혀 있게 되기 때문이다.
이는 JavaScript 객체 (내부함수)와 원시 객체 (el)간에 순환 참조를 만든다.

이 문제를 해결하기 위해서는

function addHandler() {
  var el = document.getElementById('el')
  el.onclick = function () {
    this.style.backgroundColor = 'red'
  }
  el = null
}

이렇게 하면 순환 참조 고리를 끊을 수 있다.

또, 클로져의 의해 발생한 순환 참조를 끊기 위해서는 다른 클로져를 추가하는 방법도 있다.

function addHandler () {
  var clickHandler = function () {
    this.style.backgroundColor = 'red'
  }
  (function () {
    val el = document.getElementById('el')
    el.onclick = document.getElementById('el')
    el.onclick = clickHandler
  })()
}

내부 함수는 실행되고 바로 사라지므로, clickHandler와 함께 생성된 클로져로부터 그 내용을 숨긴다.


참고

profile
Hi there 👋

0개의 댓글