JavaScript 스코프와 호이스팅


실행 컨텍스트 (Execution Context)

자바스크립트 해석기는 실행 컨텍스트라는 개념을 사용하여 구문을 해석합니다. 실행 컨텍스트는 코드가 실제로 실행되는 영역을 말하며, 실행 컨텍스트에는 전역 컨텍스트와, 각 함수가 생성한 새로운 실행 컨텍스트인 함수 컨텍스트로 나뉩니다.

전역 컨텍스트는 함수가 아닌 영역에 선언된 코드가 실행되는 컨텍스트이며, 함수 내에서 실행되는 코드들은 각자의 함수 컨텍스트를 가집니다.

이 각각의 컨텍스트에 의해 변수의 스코프가 결정됩니다.

스코프(Scope)

스코프란 프로그램의 실행 컨텍스트 (Execution Context)에서 보거나 접근할 수 있는 식별자를 말합니다.

  • 전역 스코프 - 변수가 함수 바깥에 선언되어 있거나, 변수 선언 시 var 키워드를 사용하지 않으면 변수는 전역 스코프에 포함됩니다.
  • 함수 스코프 - 변수가 함수 내에서 선언되면 이 변수는 함수 스코프 내에 존재하게 됩니다.

자바스크립트의 변수는 변수가 접근할 수 있는 범위(스코프)에 따라 전역변수와 지역변수로 나눌 수 있습니다.

전역변수는 전역 스코프, 즉 함수 바깥에서 선언된 변수입니다. 따라서 전역변수는 프로그램의 전체에서 접근 가능합니다.

반면에 지역변수는 함수 스코프, 즉 함수 내부에서 선언된 변수로, 함수 내부에서만 접근 가능합니다.

var global = 1;

function localFunc() {
  var local = 2;
  console.log(global);  // 1
  console.log(local);   // 2
}

localFunc();
console.log(global);  // 1
console.log(local);   // ReferenceError: local is not defined

위 코드와 같이 전역변수로 선언한 global 변수의 경우 함수 내부 혹은 전역에서 모두 사용 가능합니다. 하지만 함수 내부에 선언한 local 변수의 경우는 전역에서 사용하려고 하는 경우 정의되지 않았다는 메세지와 함께 참조 에러를 발생시키게 됩니다.

이렇게 변수의 유효범위를 두는 이유는 변수명의 충돌을 방지하기 위해서 입니다. 위 예제에서 local 이라는 이름의 변수는 localFunc라는 함수 내부에서만 유효하므로, 다른 함수에서도 동일한 변수명을 사용할 수 있게 됩니다. 하지만 global 이라는 변수명은 다른 곳에서 사용할 경우 문제를 일으킬 가능성이 높아지죠.

스코프 체인

만약 위 예제에서 localFunc() 함수 내부에도 global 변수를 선언하면 어떻게 될까요? 당연히 함수 내부에서 선언한 변수를 사용하게 되며 함수 내부에 global 변수가 존재하므로 굳이 전역까지 올라가서 변수를 찾지 않습니다.

이렇게 현재 유효범위 내부에서 식별자를 찾고, 없을 경우 상위 유효범위의 식별자를 찾아가는 연결고리를 스코프 체인이라고 부릅니다.

호이스팅 (hoisting)

보통 자바스크립트의 코드는 작성한 순서에 따라 윗줄부터 차례대로 실행됩니다. 하지만 변수 선언과 함수 선언의 경우는 조금 다르게 작동합니다.

아래와 같은 코드의 경우,

console.log(x);  //undefined
var x = 42;

1번째 줄은 변수 x가 선언되기 전에 호출되었으므로 오류가 발생할 것 같지만 실제로는 undefined가 출력됩니다.

이는 자바스크립트에서 변수 선언은 실행시 자바스크립트 코드의 가장 상단으로 끌어올려지기 때문입니다. 다시 말해서 변수는 코드의 중간에 선언하더라도 맨 앞에 선언한 것과 같아지며, 이를 변수 끌어올림, 호이스팅 (hoisting) 이라 합니다.

즉 위의 코드는 실행시에 아래와 같이 해석됩니다.

var x;
console.log(x);
x = 42;

이는 함수 선언 역시 마찬가지로, 함수 선언문도 실행 시 컨텍스트의 최상단으로 끌어올려집니다.

이러한 호이스팅이 발생하는 이유는 자바스크립트 해석기의 동작 방식 때문입니다. 우선 자바스크립트는 코드가 실행되면 코드를 파싱합니다. 이때 전역 컨텍스트에 전역 변수 및 함수를 등록하게 됩니다. 이때 코드를 해석해 변수 및 함수 선언문을 코드의 상단으로 끌어올리는 효과가 발생하게 됩니다.

그 후 코드를 실행하게 되는데, 이때 각각의 함수를 호출하고 함수 내부의 코드를 파싱해서 함수 컨텍스트에 함수 스코프를 가지는 변수를 등록하고 실행하는 작업을 거치게 됩니다.