[JS] Closure 정리

JunSeok·2023년 12월 18일
0

Javascript

목록 보기
12/16
post-thumbnail

Closure

  • secureBooking 함수는 로컬 스코프의 passengerCount 변수를 업데이트하는 함수를 리턴한다.
  • 전역변수 bookersecureBooking 함수의 리턴값으로 passengerCount 변수를 업데이트하는 함수를 받는다.
  • 그리고 booker 실행하면 결과가 어떻게 될까.

  • passengerCount 변수가 업데이트되고 console.log 가 실행된다.

    이미 실행이 끝난 secureBooking 의 함수 내에 선언된 passengerCount 변수를 전역 스코프의booker 함수가 사용할 수 있었던 이유가 무엇일까.

  • booker 함수는 전역 스코프에 존재하는 함수이다.

  • secureBooking 함수는 실행이 끝났기 때문에 execution context의 변수 환경도 완전히 사라졌다.

  • 하지만 booker 함수는 당시 존재했던 passengerCount 변수에 계속 접근할 수 있다.

  • booker 함수는 전역 스코프이기 때문에 scope chain으로도 passengerCount 변수에는 접근할 수 없다.

이를 가능하게 한 것은 바로 closure이다.

Closure 동작 원리

  • booker 에 할당된 함수는 call stack에서 사라진 secureBooking 함수의 execution context에서 선언되었다.
  • 하지만 booker 함수는 자신이 선언된 함수의 execution context가 사라져도 그 execution context의 변수 환경에 항상 접근할 수 있다.
  • 이러한 연결고리를 closure라 한다.
  • booker 함수는 closure를 이용하여 자신이 선언된 곳인 secureBooking 함수의 변수 환경에 항상 접근할 수 있다.

다시 정리하면, 함수는 함수 자신의 변수 환경과 자신이 선언된 함수의 변수 환경에 접근할 수 있고 이러한 연결고리를 closure라 한다.

  • 더 쉽게 말하면 부모 함수가 사라져도, 부모 함수의 모든 변수에 접근할 수 있게 해주는 것이 closure이다.
  • 이러한 closure는 부모 함수의 변수 환경에 접근하는 자식 함수가 존재하는 경우 계속 유지된다. 이때 변수의 복사본이 아니라 실제 변수에 접근한다는 것에 주의하여야 한다.
  • 자바스크립트 엔진은 로컬 스코프(자신의 변수 환경) -> closure(자신이 선언된 함수의 변수 환경) -> scope chain 순으로 변수를 탐색한다.

closure 관찰

  • closure 관련 작업은 자바스크립트가 내부적으로 처리하기 때문에 우리는 수동으로 closure 관련 작업을 할 수 없다.
  • closure는 우리가 접근할 수 있는 객체가 아니고, 명시적으로 closure에 접근할 방법 또한 없다.
  • 즉 closure는 실재하는 것이 아니다. 함수의 내부 속성일 뿐이다.

하지만 콘솔을 통해 함수 내부의 closure 속성을 직접 볼 수 있다.

  • scopes는 booker의 변수 환경을 나타내는데, closure가 우선인 것을 볼 수 있다.
  • closure를 통해 함수가 선언된 execution context가 사라져도 변수 환경을 보존할 수 있다.
  • [[]] 로 표시된 것은 내부 속성이기 때문에 코드로 접근할 수 없다는 의미이다.

한 가지 의문

closure는 자신이 선언된 부모 함수의 변수 환경에 항상 접근할 수 있다고 하는데, 부모 함수의 부모 함수, 즉 조부모 함수의 변수 환경에는 어떤 방식으로 접근될까

결과는 다음과 같다

  • 조부모 함수에서 선언된 변수 i 와 부모 함수에서 선언된 변수 j 에 정상적으로 접근할 수 있었다.
  • 또한 closure는 각각 어느 함수의 변수환경인지 명시적으로 알려주고 있으며, 부모 함수와 조부모 함수 순으로 나열되어 있다.
    이를 통해 closure가 하나가 아니라는 사실을 알 수 있었다.
    closure는 함수마다 하나씩 존재했다.
  • 즉 위 상황에서 자바스크립트 엔진이 변수를 찾는 과정이 다음과 같다.
    로컬 스코프(자신의 변수 환경) -> closure(부모 함수의 변수 환경) -> closure(조부모 함수의 변수 환경) -> scope chain 순이다.

추가 예시

예시 (1)

  • 꼭 함수를 리턴하지 않아도 된다.
  • 함수가 변수 자체일때도 closure가 발생한다.
  • 또한 변수가 재할당되면 closure 또한 변한다.
let f;
const g = () => {
  const a = 4;
  f = function() {
  	console.log(a*5)
  }
}

const h = () => {
  const b = 7;
  f = function() {
  	console.log(b*2);
  }
}

g(); // 함수 할당
f(); // 20

h(); // 함수 재할당
f(); // 14

예시 (2)

  • 타이머 또한 함수를 리턴하지 않아도 closure를 볼 수 있는 좋은 예시이다.
  • 파라미터 또한 로컬 변수인점을 기억해야 한다.
  • 함수 실행이 끝나고 execution context가 사라져도 함수는 자신이 선언된 함수의 변수 환경을 기억하기 때문에 정상 동작하는 것을 알 수 있다.
const boardPassengers = (n, wait) => {
  const perGroup = n / 3;
  setTimeOut(() => {
  	console.log(`We are now boarding all ${n} passengers`);
	console.log(`There are 3 groups. each with ${perGroup} 
  }, wait * 1000);
};

boardPassengers(180, 3);

예시 (3)

  • IIFE 패턴에서의 이벤트 콜백함수 예시에서도 closure를 볼 수 있다.
  • 콜백이 실행될 때쯤이면 IIFE 즉, 즉시 호출된 함수 식은 실행된 후 사라진다.
  • 하지만 콜백함수는 자신이 생성된 장소에서 생성된 변수에 여전히 접근할 수 있다.
(function() {
  const header = document.querySelector('h1');
  header.style.color = 'red';
  
  // 콜백함수는 여전히 header 변수에 접근할 수 있다.
  document.body.addEventListener('click', () => {
  	header.style.color = 'blue';
  })
})()

참조

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
https://poiemaweb.com/js-closure
https://www.udemy.com/course/the-complete-javascript-course/

profile
최선을 다한다는 것은 할 수 있는 한 가장 핵심을 향한다는 것

0개의 댓글