카테고리 없음

호이스팅, 클로저

김홍중 2021. 8. 12. 00:00
hoisting

호이스팅에 대한 오해

변수나 함수의 호출코드가 선언코드 아래에 있더라도 선언코드가 끌어 올려지는것으로 오해할 수 있습니다. 하지만 이는 실행 컨텍스트의 개념을 생각해보면 자연스럽게 해결됩니다.

 

변수 호이스팅

console.log('맛 : ', flavor);
var flavor = "딸기";
console.log('맛 : ', flavor);

출력

맛 : undefined
맛 : 딸기

코드 실행단계에서 코드가 로드되면 전역 컨텍스트가 생성되는데 이때에 변수는 undefined됩니다.

코드에서 변수 식별자가 나오면 해당 값을 할당하게 됩니다.

var flavor 선언 및 할당하는 코드가 위로 끌어올려지는것이 아니라 실행컨텍스트의 흐름대로 값이 할당되는 것입니다.

 

함수 호이스팅

함수 호이스팅은 함수 선언문 형태와 함수 표현식 형태가 다르게 동작합니다.

 

함수 선언문 형태

printFlavor("딸기");
 
function printFlavor(flavor) {
  console.log('맛 : ', flavor);
}

출력

맛 : 딸기

함수 선언문의 경우 함수 선언, 초기화, 사용자의 값 할당이 모두 변수 객체가 만들어지는 과정에서 완료됩니다.

그래서 오류 없이 출력됩니다.

 

함수 표현식 형태

printAge(20);
 
var printPrice = (price) => {
  console.log('가격 : ', price);
}

출력

(Uncaught ReferenceError: is not defined error)

하지만 함수 표현식인 경우는 익명함수를 생성한 뒤에 이 익명함수를 변수에 할당합니다. 

실행 컨텍스트에서 코드가 로드되어있을때 함수 선언 부분에서 함수 코드가 저장되는데 그보다 먼저 함수 호출을 하여서 오류가 발생합니다. 

 

closure
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)
클로저는 렉시컬 환경의 참조와 함수의 조합이다.

렉시컬 스코프에 대하여 간단하게 설명드리면 선언한 시점에 상위 스코프가 결정되는것입니다.

그렇다면 클로저에 대하여 예시를 통하여 알아보겠습니다.

function outterFunc(){
  const msg = 'I learned closure';  
  const innerFunc = () => {        
      console.log(msg);
  }
  innerFunc();
}

outterFunc();

출력

I learned closure

msg가 올바로 출력됨을 확인할 수 있습니다. 내부함수 innerFunc는 외부함수 outterFunc의 msg 변수에 접근할 수 있습니다. 왜냐하면 내부함수가 외부함수 내부에서 선언 되었기 때문입니다.

 

이를 스코프 체인으로 설명하면 다음과 같습니다.

 

세번째 그림에서 innerFunc함수의 스코프를 검색하여서 msg를 발견하지 못합니다.

네번째 그림에서 outterFunc함수의 스코프를 검색하여 msg를 발견합니다.

 

이번에는 외부함수 내에 내부함수를 호출하는 것이 아니라 외부함수를 반환하여 호출해보겠습니다.

function outterFunc(){
  const msg = 'I learned closure';
  const innerFunc = () => {        
      console.log(msg);
  }
  return innerFunc;
}

const inner = outterFunc();
inner();

출력

I learned clousre

 

외부함수가 반환되어도 외부함수 내의 변수를 기억하는 내부함수가 존재한다면 계속 유지됩니다. 이때 내부함수는 외부함수에 있는 변수의 복사본을 접근하는것일까요, 아니면 실제 그 변수에 접근하는 것일까요?

이것이 궁금하여서 간단한 코드를 작성해보았습니다.

function outterFunc(msg){
  return {
      getMsg : function (){
          return msg;
      },
      setMsg : function(newMsg){
          msg = newMsg
      }
  }
}

const inner = outterFunc('I learned closure');
const temp = inner;
console.log(inner.getMsg());

inner.setMsg('new text');
console.log(temp.getMsg());

출력

I learned clousre
new text

외부함수의 스코프를 기억하는 내부함수를 임시로 temp에 저장하고 inner의  해당 변수의 값을 변경하였더니 놀랍게도 temp의 해당 변수의 값도 변하는것을 확인할 수 있었습니다.

즉, 내부함수는 외부함수에 있는 실제 변수에 접근함을 알 수 있었습니다.

 

const another_inner = outterFunc('another msg');

console.log(another_inner.getMsg());

출력

// 위 출력문과 동일한 출력문. 비교를 위해 삽입
I learned clousre
new text
// anthoer_inner의 출력내용
another msg

바로 위의 코드에 이어서 또다른 outterFunc을 실행하면 어떻게 될까요? inner와 another_inner가 똑같은 외부함수를 공유 하고 있더라도 다르게 나옵니다. 외부함수가 실행 될때마다 새로운 지역변수를 출력하는 클로저가 생성되기 때문입니다.

 

참고자료

- 호이스팅

실행컨텍스트

클로저 정의

클로저 내용1

- 클로저 내용2