티스토리 뷰

모든 내용은 맨 하단의 원작자분이 작성해주신 포스팅 내용을 제가 이해할 겸 축약하여 작성한 것입니다.
문제시 알려주시면 삭제하겠습니다! 자세한 내용을 원하시면 링크를 타고 본 포스팅을 확인해주세요.

실행 컨텍스트

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다. 

실행 컨텍스트는 어떤 필요에 의해서? -> 아마 코드의 실행 순서와, 각각의 실행에서 참조해야하는 변수, 식별자 등을 효과적으로 관리하기 위한 목적으로 도입되었다.

 

실행 컨텍스트란 "실행할 코드에 제공할 환경 정보들을 모아놓은 객체" -> 정보들을 어딘가에 모아두는 이유?  A. "언젠가 필요할 때 꺼내어 쓰기 위함이다."

 

실행 컨텍스트 요소

  • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보, 선언 시점의 lexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음
  • LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨
  • ThisBinding: this 식별자가 바라봐야 할 대상 객체

-> 실행 컨텍스트를 생성할 때, VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 이를 주로 활용하게 된다.

 

모든 소스코드는 실행에 앞서 평가 과정을 거치고 코드를 실행하기 위한 준비를 한다. 즉, 자바스크립트 엔진은 소스코드를 '소스코드의 평가'와 '소스코드의 실행'으로 나눌 수 있다. 

 

소스코드 평가 과정에서는 실행 컨택스트를 생성하고 변수, 함수 등의 선언문만 먼저 실행하여 생성된 변수나 함수 식별자를 키(key)로 실행 컨텍스트가 관리하는 스코프(lexical environment의 환경 레코드)에 등록한다. 

 

이러한 준비 과정이 VariableEnvironment에 정보를 담는 과정이고, 흔히 우리가 호이스팅(Hoisting)이라고 부르는 현상이기도 하다. 

호이스팅(hoisting)은 ECMAScript® 2015 언어 명세 및 그 이전 표준 명세에서 사용된 적이 없는 용어입니다. 호이스팅은 JavaScript에서 실행 콘텍스트(특히 생성 및 실행 단계)가 어떻게 동작하는가에 대한 일반적인 생각으로 여겨집니다. 하지만 호이스팅은 오해로 이어질 수 있습니다. (MDN)
lexical environment에 대한 번역으로 어휘적, 정적 환경이라는 것은 "수시로 변하는 환경 정보를 의미하는 lexical environment에 대한 적절한 번역이라 볼 수 없다. 이보다는 '사전적인'이 더 어울리는 표현이다. (코어 자바스크립트)
  • 이전에 실행 컨텍스트의 목적을 "실행할 코드에 제공할 환경 정보들을 모아놓은 객체"라고 정의해봤다면, 그 환경 정보들을 효과적으로 찾아쓰기 위해 모아뒀다고 볼 수 있다. 찾아 쓸 때 필요한 행위가 검색이라면 lexical은 충분히 "사전적"이라는 의미로 번역될 수 있다. 

자바스크립트 엔진은 식별자에 연결된 참조를 찾을 때 실행 컨텍스트의 LexicalEnvironment를 검색한다. 가장 먼저 현재 코드가 실행되고 있는 지역 스코프에서 찾고자하는 식별자를 찾고 찾지 못한다면, 상위 스코프의 실행 컨텍스트를 차례로 타고 올라가며 글로벌 스코프까지 타고 올라가며 검색하게 된다. 끝내 원하는 식별자를 찾지 못했을 때 마주하게 되는 에러가 ReferenceError이다.


클로저(Closure)

클로저는 자바스크립트 고유 개념은 아니고, 함수형 프로그래밍 언어에서 등장하는 보편적 특성이다. 

클로저의 설명으로 MDN은 "클로저란 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다." 라고 설명하고 있다.

"클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수(free variable)라고 부른다. 클로저(closure)란 '함수가 자유 변수에 대해 닫혀있다(closed)라는 의미이다. 쉽게 의역하자면 '자유 변수에 묶여있는 함수'라고 할 수 있다." - 모던 자바스크립트 Deep Dive
"이는 함수의 변수가 유효범위 체인에 바인딩되어 있고, 따라서 그 함수는 함수의 변수에 따라 닫힌다는 뜻에서 유래한 용어이다." - 자바스크립트 완벽 가이드

Q. 클로저는 무슨 용도로 사용하나요? 

대체로 클로저는 변수가 갖고있는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다. 상태가 의도치않게 변경되지 않도록하는 것이다. 클로저의 성질을 발견한 개발자들은 상태를 안전하게 은닉하고 특정함수에게만 상태 변경을 허용하기 위해 사용하기 시작했고 함수형 프로그래밍 쪽에서 유용하게 사용되기 시작했다.

 

클로저의 동작

클로저는 대체로 중첩 함수의 함조 범위와 연관되어 있다. 함수는 자기 자신은 물론이고 상위 스코프에 속한 식별자 또한 참조할 수 있다(내부 -> 외부로 접근가능). 부모 클래스에서 갖고있는 요소를 상속 받은 자식 클래스에서 사용할 수 있는 것과 유사하다. 

 

하지만 클래스와 달리 함수는 일반적으로 호출이 종료되면서 메모리에서 스스로를 정리한다. 정확히는 더이상 참조하지 않는 식별자들에 대해, 즉 참조 카운트가 0인 식별자에 대해 GC(가비지 콜렉터)가 메모리를 해제해주면서 함수는 더 이상 메모리에 존재하지 않고, 그와 함께 함수가 실행되면서 생성되었던 실행 컨텍스트 역시 함께 정리된다.

 

호이스팅 또한 유사한 방식으로 설명할 수 있다. 

이렇나 동작을 클로저라는 하나의 개념으로 종합했을 때 다음과 같은 정의가 가능하다.

"외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다." -모던 자바스크립트 Deep Dive
"자신이 생성될 때의 스코프에서 알 수 있었던 변수들 중 언젠가 자신이 실행될 때 사용할 변수들만을 기억하여 유지시키는 함수" - 함수형 자바스크립트 프로그래밍

클로저의 매커니즘을 정확한 용어를 사용하여 정리하면 아래와 같다.

 

"스코프의 실체는 실행 컨텍스트의 렉시컬 환경이다. 이 렉시컬 환경은 자신의 '외부 렉시컬 환경에 대한 참조(outer lexical environment reference)'를 통해 상위 렉시컬 환경과 연결된다. 이것이 바로 스코프 체인이다. 
따라서 '함수의 상위 스코프를 결정한다'는 것은 '렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다는' 것과 같다." -모던 자바스크립트 Deep Dive 

실행 컨텍스트의 렉시컬 환경인 스코프는 자기 스스로에 대한 참조 정보를 가지고 있으면서 동시에, 자신이 속해있는 환경인 상위 스코프의 '외부 렉시컬 환경에 대한 참조'를 갖고 있다. 마치 노드마다 next를 갖고있는 링크드 리스트 구조와 같기 때문에 스코프를 단방향 링크드 리스트 구조로 설명하기도 한다. 이 실행 컨텍스트가 링크드 리스트 구조로 이어지며 발생하는 형태를 "스코프 체인"이라 부른다. 

 

앞서 언급한 중첩함수외부 함수(outer function)를 갖고 있는 내부 함수(inner function)형태로 바꾸어 말할 수 있다.

핵심은 외부 함수의 생명 주기가 종료되어 실제로 outer의 실행 컨텍스트는 제거되지만, 식별자를 포함하고 있는 렉시컬 환경까지 사라지지는 않는다는 점이다. 앞서 실행 컨텍스트를 설명하며 이 렉시컬 환경은 식별자를 찾아볼 수 있는 사전과 같다고 말했다. 

"outer 함수의 실행이 종료하면 inner 함수를 반환하면서 outer 함수의 생명 주기가 종료된다. 즉, outer 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 제거된다. 이때 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 outer함수의 렉시컬 환경까지 소멸하는 것은 아니다." -모던 자바스크립트 Deep Dive

모든 자바스크립트 요소는 내부 슬롯을 갖는데, 함수의 경우 여러 내부 슬롯 중 [[Environment]] 내부 슬롯을 갖는다. 개발자가 직접 접근할 수는 없지만 자바스크립트 엔진은 함수의 [[Environment]] 내부 슬롯에 저장되어 있는 상위 스코프에 대한 참조를 활용하여 외부 함수 렉시컬 환경에 접근한다. 

 

클로저의 활용 

클로저의 여러 활용으로 은닉 같은 속성이 많이 언급되는데, 함수형 프로그래밍 측면에서는 맥락을 유지할 수 있도록 내부 변수를 남겨둘 수 있다는 점에서 중요하게 여겨지는 거 같다. 

외부에 대한 제한된 인터페이스를 구성할 수 있도록 상태를 함수 안쪽으로 은닉하는 것도 중요한 활용의 예라고 할 수 있다. 아래는 클로저를 활용해 카운트를 안전하게 증감시키도록 메서드를 구현한 경우이다.

 

상태(예시에서는 num 변수)를 안전하게 은닉한 number counter 예시 

{
  let num = 0;

  const increase = function () {
    return ++num;
  };
}
{
  // num의 상태가 유지되지 못함
  const increase = function () {
    let num = 0;

    return ++num;
  };
}
{
  // num의 상태가 유지됨
  const increase = (function () {
    let num = 0;

    return function () {
      return ++num;
    };
  })();
}
{
  // num에 대한 증가, 감소 함수 구현
  const numCounter = (function () {
    let num = 0;

    return {
      increase() {
        return ++num;
      },
      decrease() {
        return num > 0 ? --num : 0;
      },
    };
  })();

  numCounter.decrease();
  numCounter.increase();
}

 

 

all ref: https://saengmotmi.netlify.app/javascript-study/2021-08-05-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8/

 

2021-08-05 실행 컨텍스트

자바스크립트 동작의 시작이자 끝

saengmotmi.netlify.app

https://saengmotmi.netlify.app/javascript-study/2021-09-01-%ED%81%B4%EB%A1%9C%EC%A0%80/

 

2021-09-01 클로저

자신이 생성될 때의 스코프에서 알 수 있었던 변수들 중 언젠가 자신이 실행될 때 사용할 변수들만을 기억하여 유지시키는 함수

saengmotmi.netlify.app

 

댓글