✅ 클로저란

🔥 클로저란 어떤 함수A에서 선언한 변수a를 참조하는 내부함수B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수a가 사라지지 않는 현상을 말한다.

어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이, 이말은 ❗️외부 함수의 렉시컬환경이 가비지 컬렉팅되지 않는 현상이라는 말과 같다.

👉 가비지 컬렉터 : 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스를 말한다. 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않음.

var outer = function(){
  var a =1;
  var inner = function(){
    return ++a;
  }
  return inner
}

var outer2 = outer();
console.log(outer2()) //2
console.log(outer2()) //3

outer함수 종료시 inner 함수 자체를 반환했다.
그러면 outer 함수의 실행 컨텍스트가 종료될 때 outer2변수는 outer의 실행 결과인 inner 함수를 참조하게 될 것이다.
-> inner함수가 선언된 위치의 렉시컬환경이 참조복사된다.

outer함수가 종료된 상태인데 outer함수의 렉시컬환경에 접근할 수 있었던 이유는 가비지 컬렉터의 동작 방식때문이다.


✅ 클로저와 메모리 관리

👉 메모리 누수 : 개발자의 의도와 달리 어떤 값의 참조 카운트가 0이 되지 않아 가비지 컬렉터의 수거대상이 되지 않는 경우

클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수를 메모리를 소모하도록 함으로써 발생한다.

필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 참조카운트를 0으로 만들어 메모리 소모에 대해 관리한다.

참조 카운트를 0으로 만드는 방법은 식별자에 기본형 데이터(null이나 undefined)를 할당한다.

var outer = function(){
  var a =1;
  var inner = function(){
    return ++a;
  }
  return inner
}

console.log(outer2())  
// function(){
//  return ++a;
// }
outer = null; 👉 outer 식별자의 inner함수 참조를 끊음

console.log(outer())
// "TypeError: outer is not a function ...

✅ 클로저의 활용사례

1️⃣ 콜백함수 내부에서 외부 데이터를 사용하고자 할 때

❶ 콜백함수를 내부함수로 선언해서 외부 변수를 직접 참조

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');


fruits.forEach(function(fruit){
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', function(){
    alert('your choice is' + fruit);
  })
  $ul.appendChild($li)
})

document.body.appendChild($ul);

❷ bind 활용하기

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

var alertFruit = function(fruit){
  alert('your choice is' + fruit);
}

fruits.forEach(function(fruit){
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruit)
  $ul.appendChild($li)
})

document.body.appendChild($ul);

이번에는 공통 함수로 쓰기위해 alertFruit를 외부로 꺼냈다.
그런데 각 li를 클릭하면 [object PointerEvent]이라는 값이 출력된다.
이러한 이유는 콜백 함수의 인자에 대한 제어권을 addEventListener가 가진 상태고, addEventListener는 콜백 함수를 호출할 때 첫번째 인자에 이벤트 객체를 주입하기 때문이다.

이 문제는 bind메서드를 사용해 해결할 수 있다.


fruits.forEach(function(fruit){
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruit.bind(null, fruit))
  $ul.appendChild($li)
})

다만 이렇게 하면 this가 원래 함수내부에서의 this가 달라지는 점은 감안해야 한다. 이런 변경사항이 발생하지 않게 하기위해서는 고차함수를 활용해야 한다.

❸ 고차함수 사용하기

var alertFruit = function(fruit){
  return function(){
  alert('your choice is' + fruit);
  }
}

fruits.forEach(function(fruit){
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruit(fruit))
  $ul.appendChild($li)
})

alertFruit함수가 실행되면서 fruit값을 인자로 전달하는데 그러면 이 함수의 실행 결과가 다시 함수가 되며 이렇게 반환된 함수를 리스너에 콜백 함수로써 전달할 것이다.

이후 클릭 이벤트가 발생하면 이 함수의 실행 컨텍스트가 열리면서 alertFruit의 인자로 넘어온 fruit를 outerEnvironmentReference에 의해 참조 할 수 있다.
즉 alertFruit 함수의 실행 결과로 반환된 함수에는 클로저가 존재한다.


2️⃣ 접근 권한 제어(정보은닉)

접근 권한에는 public, private, protected의 3종류가 있다.

  • public : 외부에서 접근 가능
  • private : 내부에서만 사용하고 외부에 노출되지 않음

💡 클로저를 이용하면 함수차원에서 public한 값과 private한 값을 구분할 수 있다.

var outer = function(){
  var a =1;
  var inner = function(){
    return ++a;
  }
  return inner
}

var outer2 = outer();
console.log(outer2()) //2
console.log(outer2()) //3

이 예제를 보면 외부에서는 오직 outer함수가 return한 정보에만 접근할 수 있다. 즉 return값이 외부에 정보를 제공하는 유일한 수단이다.

🔥 return한 변수들은 공개멤버가 되고, 그렇지 않은 변수들은 비공개 멤버가 되는 것이다.


3️⃣ 부분 적용 함수

profile
멋찐 프론트엔드 개발자가 되자!

0개의 댓글