[코어 자바스크립트] 5장 - 클로저

Yongwoo Cho·2022년 6월 20일
0

TIL

목록 보기
96/98
post-thumbnail

5장 - 클로저

var outer = function () {
  var a = 1;
  var inner = function () {
    return ++a;
  };
  return inner;
};
var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3

❓ inner 함수의 실행 시점에는 outer 함수는 이미 실행이 종료된 상태인데 outer 함수의 렉시컬환경에 어떻게 접근할 수 있는 걸까?

👉 가비지 컬렉터의 동작 방식 때문이다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않는다.

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

❗ '외부로 전달'이 곧 return 만을 의미하는 것은 아니다

(function () {
  var a = 0;
  var intervalId = null;
  var inner = function () {
    if (++a >= 10) clearInterval(intervalId);
    console.log(a);
  };
  intervalId = setInterval(inner, 1000);
});

별도의 외부객체인 window의 메서드에 전달할 콜백 함수 내부에서 지역변수를 참조하므로 클로저이다.

✔️ 메모리 누수와 관리 방법

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

관리 방법 : 클로저의 필요성이 사라진 시점에는 더는 메모리를 소모하지 않게 해주면 된다. 즉 참조 카운트를 0으로 만들면 카비지 컬렉터가 메모리를 회수해간다.

❓ 어떻게 참조 카운트를 0으로 만들까?

👉 식별자에 참조형이 아닌 기본형 데이터(null이나 undefined)를 할당하면 된다.

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

✔️ 활용 사례

  • 콜백 함수 내부에서 외부 데이터를 사용하고자 할 때
var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

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

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

alertFruitBuilder의 실행 결과로 반환된 함수에는 클로저가 존재한다.

  • 접근 권한 제어(정보 은닉)

정보은닉 : 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나이다. 클로저를 이용하면 함수 차원에서 public한 값과 private한 값을 구분하는 것이 가능하다.

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

var outer2 = outer();
console.log(outer2());

outer 함수를 종료할 때 inner 함수를 반환함으로써 outer 함수의 지역변수인 a의 값을 외부에서도 읽을 수 있게 됐다. 이처럼 클로저를 활용하면 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한을 부여할 수 있다.

외부에 제공하고자 하는 정보들을 모아서 return하고, 내부에서만 사용할 정보들은 return 하지 않는 것으로 접근 권한 제어가 가능하다. return한 변수들은 public, 그렇지 않은 변수들은 private이 된다.

  • 부분 적용 함수

부분 적용 함수란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수다.

var add = function () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};

var addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55

addPartial 함수는 인자 5개를 미리 적용하고, 추후 추가적으로 인자들을 전달하면 모든 인자를 모아 원래의 함수가 실행되는 부분 적용 함수이다.

// 부분 적용함수로 debounce 구현하기
var debounce = function (eventName, func, wait) {
  var timeoutId = null;
  return function (event) {
    var self = this;
    console.log(eventName, "event 발생");
    clearTimeout(timeoutId);
    timeoutId = setTimeout(func.bind(self, event), wait);
  };
};

var moveHandler = function (e) {
  console.log("move event 처리");
};

var wheelHandler = function (e) {
  console.log("wheel event 처리");
};
document.body.addEventLister("mousemove", debounce("move", moveHandler, 500));
document.body.addEventLister(
  "mousewheel",
  debounce("wheel", wheelHandler, 700)
);

클로저로 처리되는 변수 : eventName, func, wait, timeoutId

  • 커링 함수

커링 함수란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말한다. 커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 하고 중간 과정상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기만 할 뿐으로, 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않는다.

var curry3 = function (func) {
  return function (a) {
    return function (b) {
      return func(a, b);
    };
  };
};

var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25

원하는 시점까지 지연시켰다가 실행하는 것이 요긴한 상황이라면 커링을 쓰기에 적합하다. 혹은 프로젝트 내에서 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 적절한 후보가 된다.

profile
Frontend 개발자입니다 😎

0개의 댓글