렉시컬 스코프, 클로져, 변수, 호이스팅 정리

📅 TIL #129




🎯 Achievement Goals

  1. 렉시컬(lexical) 스코프에 대해서 이해한다.
  2. 클로져에 대해서 이해한다.
  3. 변수 선언, 초기화, 할당의 차이점에 대해 이해한다.
  4. 호이스팅과 TDZ이 어떻게 연관되어있는지 설명한다.







렉시컬 스코프(Lexical Scope)


렉시컬 스코프를 알아보기 전에 간단하게 스코프에 대해서 정리하자면, 스코프는 단순하게 범위라는 뜻보다는 식별자가 어느 범위까지 참조가 가능한가이다.


스코프는 어느 위치에서 어떻게 선언을 했는지에 따라 스코프가 결정된다. 선언 방식에는 var, let, const 등이 있다. var는 함수 스코프를 따르며 가장 가까운 함수를 유효 범위로 가지며, letconst는 블록 스코프를 따르면서 가장 가까운 블록을 유효 범위로 가진다. (선언 키워드에 대한 자세한 내용은 밑에서 더 다루도록 한다.)


var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?


위 코드의 실행 결과는 함수 bar의 상위 스코프가 무엇인지에 따라 결정된다. 여기서 함수를 어디서 호출하였는지, 혹은 어디서 선언하였는지에 따라 상위 스코프를 결정한다.


위의 방식에서 첫번째 방식을 동적 스코프(Dynamic scope)라 하고, 두번째 방식을 렉시컬 스코프(Lexical scope)또는 정적 스코프(Static scope)라고 한다. 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.


정리하자면 렉시컬 스코프는 함수를 어디서 선언했는지에 따라 결정된다. 위의 코드에서 함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다. 현재 bar는 전역에서 선언 했고, 상위 스코프는 전역 변수 x이며 값은 1을 출력한다.






클로져 (Closure)


function outerFunc() {
  let x = 10;
  let innerFunc = function () {
    console.log(x);
  };
  return innerFunc;
}

/**
 *  함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
 *  그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
 */
let inner = outerFunc();
inner(); // 10


클로져란 자신을 포함하고 있는 외부 함수보다 내부 함수가 더 오래 유지 되는 경우, 외부 함수 밖에서 내부 함수가 호출 되더라도 외부 함수의 지역 변수에 접근할 수 있는 내부함수를 뜻한다.


MDN 정의
클로져는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.


클로져는 반환된 내부 함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수를 말한다.


실행 컨텍스트 관점에서 설명하면, 내부 함수가 유효한 상태에서 외부 함수가 종료하여 외부 함수의 실행 컨텍스트가 반환되어도, 외부 함수 실행 컨텍스트 내의 활성 객체(Activation object)는 내부 함수에 의해 참조되는 한 유효하여 내부 함수가 스코프 체인을 통해 참조할 수 있는 것을 의미한다.


즉 외부 함수가 이미 반환 되었어도 내부 함수가 하나 이상 존재한다면 외부 함수의 변수는 계속 유지된다. 이때 내부 함수가 접근하는 외부 함수의 변수는 복사본이 아니라 실제 변수에 접근한다.


closure






변수 선언, 초기화, 할당의 차이점


변수 선언은 선언 키워드(var, let, const)를 사용하여 값이 저장되는 공간을 만드는 것을 의미한다. 변수 선언시, 변수 선언을 하고 나면 할당할 수 있는 공간이 생기는데, 할당 연산자를 사용하여 선언한 변수명을 적고 변수를 할당할 수 있다.


초기화도 할당 개념에 포함한다. 하지만 변수 선언 후 최초로 값을 할당할 때 사용한다는 차이점이 있다.


변수 선언 단계에서 var는 초기화 단계가 한번에 발생하고, let, const 같은 경우는 초기화 단계가 분리되어 있다.


선언 단계와 초기화 단계의 중간에서 변수를 조회할 수 없는 구간을 TDZ(Temporal Dead Zone)라고 부른다.






TDZ(Temporal Dead Zone)


TDZ는 자바스크립트에서 변수를 선언하고 난 후, 초기화되지 않은 상태까지의 빈 코드 공간을 의미 한다.


{
  // let, const 등으로 선언한 변수는 에러가 난다.
  // var 같은 경우는 에러가 안나고, undefined를 출력한다.

  console.log(myAge); // throws ReferenceError

  // TDZ
  // TDZ
  // TDZ
  // TDZ
  // TDZ

  let myAge = 28;
  console.log(age); // 28
}


특징을 간략하게 정리해 보자면

  1. var, let, const 등으로 선언한 모든 변수는 hoistring 이 된다.
  2. let과 const로 hoisting한 변수는 초기화전에 조회할 수 없다.
  3. var로 hoisiting한 변수는 초기화 전에 조회할 수 있다. (하지만 언제나 undefined)
  4. 실질적으로 let으로 hoisting한 변수는 초기화 전까지 중간에 활용할 수 있는 방법은 없다.






[추가] 호이스팅(hoisting)


함수 안에 있는 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것


함수 내에서(함수 블록 {}안에서) 존재하는 내용 중 필요한 값들을 끌어 올려서 실행시킨다. 실제로 코드가 끌어 올려지는 뜻은 아니며, 자바스크립트 Parser 내부적으로 끌어 올려서 처리한다.


호이스팅의 대상


var 변수 선언함수 선언문에서만 호이스팅이 일어난다.


// 함수 선언식 vs 함수 표현식

foo();
bar();

// 함수 선언식
function foo() {
  console.log("hello foo");
}

// 함수 표현식
var bar = function () {
  console.log("hello bar");
};


호이스팅의 우선 순위


// 변수 선언이 함수 선언보다 위로 끌어 올려진다.

// 1. 변수 선언
var myName;
var yourNmae;

// 2. 함수선언문
function myName() {
  console.log("useonglee");
}
function yourName() {
  console.log("everyone");
}

// 3. 변수값 할당
myName = "hi";
yourName = "bye";

console.log(typeof myName); // > "string"
console.log(typeof yourName); // > "string"


// 값 할당했을 때와 안했을 때의 변수 호이스팅

var myName = "useonglee"; // 값 할당
var yourName; // 값 할당 X

function myName() {
  console.log("myName Function");
}

function yourName() {
  console.log("yourName Function");
}

console.log(typeof myName); // > "string"
console.log(typeof yourName); // > "function"





참고 자료

클로져(Closure)
호이스팅(hoisting)이란?