ARTICLE | [JS] 클로저

noopy·2021년 8월 21일
0

📃 ARTICLE

목록 보기
4/5
post-thumbnail
post-custom-banner

🍡 클로저

함수와 그 함수가 선언됐을 당시의 렉시컬 환경과의 조합(combination)

실행 컨텍스트 간단 복습

실행컨텍스트 환경 정보

실행컨텍스트: JS 엔진이 동일한 환경에 있는 코드를 실행하기 위해
필요한 정보(환경 정보)를 수집하는데, 이러한 정보들을 모아놓은 객체

  • variableEnvironment: 식별자 정보, 외부환경 정보 초기화
  • LexicalEnvironment: 식별자 정보, 외부환경 정보 수집
  • ThisBinding: this 바인딩

outer-LexicalEnvironment

실행 컨텍스트가 활성화되면 outer-LexicalEnvironment에서 자신이 선언된 스코프의 LexiclEnvironment를 참조 복사한다.
모든 함수는 [[Environment]] 라는 숨김 프로퍼티를 갖는데,
이곳에 참조가 저장된다.

스코프 체이닝

해당 실행 컨텍스트의 렉시컬 환경(environmentRecord)에 식별자 정보가 없을 경우, outer-LexicalEnvironment에서 참조 복사된 상위 스코프의
LexicalEnvironment에서 식별자 정보를 찾는다.

사례 1

const outer = function () {
  let a = 1;
  const inner = fuction () {
    console.log(++a);
  };
  inner();
};
outer();

사례 2

const outer = function () {
  let a = 1;
  const inner = function () {
    return ++a;
  };
  return inner();
};
const outer2 = outer();
console.log(outer2);

사례 3

결과적으로 outer2엔 inner 함수를 참조.

  • environmentRecord: 비어있음
  • outer-LexicalEnvironment: outer L.E 참조

outer 함수는 이미 실행이 종료됐는데 outer L.E에 접근할 수 있는걸까?

가비지 컬렉터가 도달 가능성을 판단하고 수집 대상에 포함 X.

🍡 클로저 의미

어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상
👇🏻
외부 함수의 LexicalEnvironment가 가비지 컬렉팅되지 않는 현상
👇🏻
어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우,
A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상.

외부로 전달?

외부로 전달: 지역 변수를 참조하는 내부 함수를 외부에 전달
1. 리턴
2. 콜백으로 전달

사례 1

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

지역변수를 참조하는 내부 함수: inner 함수
지역변수: a, intervalId
⭐️ setTimeout, setInterval: window의 메서드 (별도의 외부 객체)

사례 2

(function () {
  let count = 0;
  let button = document.createElement('button');
  button.innerText = 'click';
  
  button.addEventListener('click', () => {
  	console.log(++count, 'times clicked');
  });
  document.body.appendChild(button);
})();

지역변수를 참조하는 내부 함수: addEventListener의 콜백 함수
지역변수: count
⭐️ addEventListener: DOM의 메서드 (별도의 외부 객체)

🍡 클로저와 메모리 관리

가비지 컬렉터는 어떤 값의 참조 카운트가 0이 될 경우 수거 대상으로 지정한다.
클로저로 인해 계속해서 지역변수를 참조하고 있을 경우,
메모리가 소모되므로 참조 카운트를 0으로 만들자!

방법

참조형이 아닌 기본형 데이터 할당.

const outer = (function () {
  let a = 1;
  let inner = function () {
  	return ++a;
  };
  return inner;
})();
console.log(outer());
console.log(outer());
outer = null; // ⭐️ 참조 해제

outer 식별자의 inner 함수 참조를 끊음.

(function () {
  let a = 0;
  let intervarId = null;
  const inner = function () {
  	if (++a >= 10) {
      clearInterval(intervalId);
      inner = null; // ⭐️ 참조 해제
    }
    console.log(a);
  };
  intervalId = setInterval(inner, 1000);
})()

inner 식별자의 함수 참조를 끊음.

(function () {
  let count = 0;
  let button = document.createElement('button');
  button.innerText = 'click';
  
  const clickHandler = function () {
  	console.log(++count, 'times clicked');
    if (count >= 10) {
      button.removeEventListener('click', clickHandler);
      clickHandler = null; // ⭐️ 참조 해제
    }
  }
  
  button.addEventListener('click', clickHandler);
  document.body.appendChild(button);
})();

EventListener는 보통 콜백함수를 handler로 쓰는데,
무명 콜백 함수를 유명함수로 바꿔 null | undefined을 할당할 수 있도록 만들기.

🍡 클로저 활용하기

외부로 전달: 지역 변수를 참조하는 내부 함수를 외부에 전달

콜백함수 내부에서 외부 변수 참조하기

1. 직접 참조하기 (클로저 사용)

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

fruits.forEach(fruit => { // 콜백함수 A
  const $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', () => { // 콜백함수 B
    alert('your choice is' + fruit);
  });
  $ul.appendChild($li);
});
document.body.appendChild($ul);
  1. fruits 배열에서 forEach의 콜백함수(A)로 순회
  2. 콜백함수 A 내부에서 forEach가 순회되는 것과 별개로,
    별도의 외부 객체인 addEventListener의 콜백함수 B가 클릭 이벤트마다 실행.
  3. 콜백함수 B는 외부 함수인 콜백함수 A의 fruit 인자를 참조.
    => 외부 함수의 지역 변수를 참조하는 내부함수를 외부로 전달 = 클로저
  4. 콜백함수 B는 자신의 렉시컬 환경에서 fruit 식별자가 없으므로,
    상위 스코프의 렉시컬 환경에서 식별자를 찾는다.

2. 클로저 사용 안하기

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

const alertFruit = fruit => // 공통함수로 쓰기 위해 변수에 담음
  alert('your choice is' + fruit);

fruits.forEach(fruit => { // 콜백함수 A
  const $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruit); // ⭐️ 인자 바꿔주기
  $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);

고차 함수 활용하기 (클로저 사용)

고차함수: 함수를 인자로 받거나, 함수를 리턴하는 함수

const alertFruitBuilder = fruit => {
  return () => alert('your choice is' + fruit);
};
// 함수를 리턴

fruits.forEach(fruit => { // 콜백함수 A
  const $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruitBuilder(fruit)); // 리턴된 함수가 
  $ul.appendChild($li);
});
document.body.appendChild($ul);

alertFruitBuilder는 함수를 리턴.
👇🏻
리턴된 함수는 EventListener의 콜백함수로 사용됨.
👇🏻
언젠가 클릭 이벤트가 발생하면, alertFruitBuilder 실행컨텍스트가 활성화되며
인자로 넘어온 fruitouter-LexicalEnvironment에 의해 참조.

profile
💪🏻 아는 걸 설명할 줄 아는 개발자 되기
post-custom-banner

0개의 댓글