WEB/JavaScript

[JavaScript] 호이스팅, 스코프

developer of the night sky 2025. 1. 5. 20:18

자바스크립트 호이스팅과 스코프

  • 자바스크립트 엔진에서는 코드를 실행하기 전에 변수와 함수 선언을 먼저 처리한다.
  • 이로 인해 마치 선언이 코드의 최상단으로 끌어올려진 것처럼 동작하는 것을 호이스팅이라고 부른다.
  • 또한 변수나 함수가 어떤 범위(scope)에서 유효한지에 따라 여러 가지 특징적인 동작을 보인다. 

호이스팅

  • 자바스크립트 엔진은 코드를 실행하기 이전에, 변수 선언문이나 함수 선언문을 미리 해석하여 선언을 위한 메모리를 할당한 뒤, 코드가 실행될 때 변수나 함수를 미리 사용할 수 있게 준비해 둔다.
  • 예시: 
console.log(score); // undefined
var score;
score = 100;
console.log(score); // 100

 

  • 일반적으로 "아직 선언되지 않은 변수를 console.log로 출력하면 에러가 발생해야 하는 것 아닌가?"라고 생각할 수도 있지만, 자바스크립트에서는 위 예시처럼 `undefined`가 출력된다.
  • 이는 `var score;` 선언이 런타임 이전에 미리 처리되었기 때문이다. 이처럼 선언이 코드의 최상단으로 끌어올려진 것처럼 동작하는 것을 호이스팅이라고 부른다.

 

변수 호이스팅

  • 위 예시에서 `var` 키워드를 사용한 변수는 선언과 동시에 초기화가 되므로 `undefined`가 출력되었다.
  • `let`, `const`로 선언한 경우에는 어떤 결과가 될까?
console.log(age); // ReferenceError
let age = 30;
  • 이 경우, age 변수가 호이스팅되었더라도, 초기화는 실제 코드가 도달했을 때 이루어지므로 그 이전 시점에 참조하려고 하면 에러가 발생한다.

 

함수 호이스팅

  • 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 현상을 함수 호이스팅이라고 일컫는다. 하지만 변수 호이스팅과는 미묘한 차이가 존재한다.
// 함수 참조
console.dir(add); // f add(x, y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function

// 함수 선언문
function add(x, y) {
  return x + y;
}

// 함수 표현식
var sub = function (x, y) {
  return x - y;
};

 

  • 함수 선언문을 통해 정의된 함수는 런타임 이전(평가 단계)에 함수 객체가 생성되고, 식별자에 할당되어 있다.
    • 따라서 코드 최상단에 선언된 것처럼 동작하여 함수 호출을 해도 문제가 없다.
  • 함수 표현식은 변수에 함수를 할당하는 문이므로, 변수 호이스팅만 일어나고 함수 객체 자체는 런타임 시점에 할당된다.
    • 따라서 초기화 이전에 함수를 호출하면 에러(TypeError)가 발생하게 된다.

 


스코프(Scope)

  • 변수나 함수가 유효하게 작동하는 범위
  • "코드 어디에서 해당 변수/함수에 접근할 수 있는가?"를 정하는 영역
  • 스코프는 크게 전역 스코프지역 스코프로 구분된다.
    • 전역 스코프: 코드 어디서든 접근 가능한 범위
    • 지역 스코프: 특정 코드 블록(함수, 블록 등) 내부에서만 접근 가능한 범위

 

스코프 체인(Scope Chain)

  • 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 현재 스코프에서 시작하여 상위(외부) 스코프 방향으로 이동하며 변수를 검색한다.
  • 예시:
const x = "global x";
const y = "global y";

function outer() {
  const y = "outer's local y";

  function inner() {
    const z = "inner's local z";
    console.log(x); // "global x"
    console.log(y); // "outer's local y"
    console.log(z); // "inner's local z"
  }
  
  inner();
  // console.log(z); // ReferenceError: z is not defined
}

outer();
  • inner 함수 내부에서는 outer 함수에 정의된 y에 접근이 가능하지만, outer 함수 내부에서는 inner 함수의 z에 접근할 수 없다. 이것이 스코프 체인과 렉시컬 스코프(정적 스코프)의 특징이다.
  • 해당 코드에 대한 스코프 체인은 아래 그림과 같이 구성되어 있다.

 

스코프 레벨

1. 함수 레벨 스코프

 

  • var 키워드로 선언된 변수나, 함수 선언문으로 만들어진 함수는 함수 레벨 스코프를 갖는다고 볼 수 있다.
  • 이는 "함수 내부 전체에서만 유효"하다는 의미이다.

 

function foo() {
  if (true) {
    var color = 'blue';
  }
  console.log(color); // blue
}
foo();

 

2. 블록 레벨 스코프

  • `let`이나 `const`로 선언한 변수는 블록 레벨 스코프를 따르므로, 해당 블록(`{ ... }`) 내부에서만 유효하며 블록을 벗어나면 참조할 수 없다.
function foo() {
  if (true) {
    let color = 'blue';
    console.log(color); // blue
  }
  console.log(color); // ReferenceError
}
foo();

함수 레벨 스코프와 블록 레벨 스코프

 

3. 렉시컬 스코프

  • “함수의 선언 위치에 의해 상위 스코프가 정해진다”는 언어적 특성 또는 개념이다.
  • 정적 스코프(Static Scope) 라고도 불린다.

 

렉시컬 스코프의 동작

  • 함수가 선언될 당시의 스코프 체인이 함수의 상위 스코프로 결정된다.
  • 함수가 호출된 위치는 스코프 결정에 영향을 미치지 않는다. 
  • 예시:
var a = 10;

function first() {
  var a = 20;
  second();
}

function second() {
  console.log(a);
}

first(); // ?
second(); // ?
  • 실행 결과로는 10, 10이 출력된다. second함수가 first함수 안에서 호출되었다고 하더라도 second함수가 선언된 위치는 전역 스코프이므로 전역 변수인 a를 참조하여 10이 출력되는 것이다.
  • 이러한 개념은 클로저와 매우 밀접하게 연관되어 있다. 클로저는 다음 게시물에서 확인해본다.

 

 

 

참고

https://velog.io/@ahhpc2012/javascript-scope