Notice
Recent Posts
Recent Comments
Link
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

mimizae 님의 블로그

JavaScript의 Scope에 대해서 (2) 본문

JS

JavaScript의 Scope에 대해서 (2)

mimizae 2025. 10. 26. 17:28

이전 글을 읽고 와 주시면 감사하겠습니다! 👍🏻

 

호이스팅(Hoisting)이란?

JS의 호이스팅이란 변수 선언과 함수 선언이 코드의 맨 위로 끌어올려지는 현상을 말한다. 그래서, 개발자가 어느 위치에 선언을 해도 실행 되기 전, 코드가 최상단으로 끌어올려지고 실행되게 된다.

 

호이스팅은 JS 엔진이 실행 전에 변수와 함수 선언을 먼저 메모리에 등록하기 때문에 발생한다.
이 과정에서 변수는 선언(생성)과 초기화가 분리되어 처리되기 때문에, 실행 시점에 따라 undefined가 출력되거나 ReferenceError가 발생한다.

 

우선 간단히 호이스팅의 동작을 코드 예제로 살펴보자.

console.log(a()); // 'a'
console.log(b()); // Uncaught TypeError: b is not a function
console.log(c()); // Uncaught TypeError: b is not a function
 
function a() {
    return 'a';
}
 
var b = function bb() {
    return 'bb';
}
 
var c = function() {
    return 'c';
}

 

딱 봐서는, a가 출력되면 안 될 것 같지만!!!! 출력이 된다.
실제 컴파일이 호이스팅에 의해 아래와 같이 이루어지기 때문이다.

function a() {
    return 'a';
}
var b;
var c; // 함수 선언은 위로 강제로 끌어져 올라온다. (호이스팅)

console.log(a());
console.log(b());
console.log(c());
 
b = function bb() {
    return 'bb';
} // 선언 이후의 할당

c = function() {
    return 'c';
} // 선언 이후의 할당

 

이처럼 함수 선언문(function a())과 변수 선언문(var b, var c)이 코드 실행 전에 모두 호이스팅되지만,
함수 선언문은 함수 전체가 메모리에 등록되는 반면, 변수 선언문은 선언만 호이스팅되고(메모리에 등록되고) 값은 아직 할당되지 않았기 때문에


console.log(a())는 정상적으로 실행되고, b()와 c()는 아직 함수가 아닌 undefined 상태로 인식되어 오류가 발생한다.

 

 

함수 호이스팅

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

// 함수 표현식
const add = function(x, y) {
  return x + y;
};

 

JavaScript에서는 함수를 정의하는 방법으로 함수 선언식함수 표현식 두 가지가 있다.

두 방법 모두 함수를 정의한다는 점에서는 같지만, 함수 표현식은 함수를 변수에 할당하기 때문에 더 유연하게 사용할 수 있다.
또한, 함수 표현식은 변수 선언부만 호이스팅되고 함수의 실제 정의(할당)는 호이스팅되지 않기 때문에,
선언식처럼 “선언 이전에 호출되는 혼동”을 방지할 수 있다. (선언식은 함수 자체가 메모리에 등록 됨)

console.log(add(2, 3)); // 5
 
function add(x, y) {
    return x + y; 
}
 
console.log(add(3, 4)); // 7

 

함수 선언식의 예제이다.

add(2, 3)은 아직 function add() 함수가 정의되지 않았음에도 불구하고 add() 함수를 호출하는 것이 가능하다.

코드 해석 단계에서 호이스팅이 일어나 function add() 가 상단으로 끌어올려졌기 때문이다.

 

console.log(add(2, 3));  // error
 
// 함수 표현식 형태로 add() 함수 정의
var add = function (x, y) {
    return x + y;
 
}
 
console.log(add(3, 4));  // 7

 

반면 이 예제에서 add 함수는 함수 표현식으로 정의 되어있어 호이스팅이 강제로 일어나지 않아 변수 add에 함수가 할당 되기 전에 add 함수를 호출했기에 에러가 뜬다.

 

 

변수 호이스팅

 

변수는 선언 키워드가 var, let, const 중 무엇이냐에 따라 서로 다른 호이스팅 동작을 보인다.

 

우선 문제의 var을 살펴 보자. 이는 이전 글에서도 잠깐 언급한 바 있다.

var globalNum = 10;     // globalNum을 전역 변수로 선언함.

function printNum() {
    document.write(globalNum);
    var globalNum = 20; // globalNum을 지역 변수로 선언함.
    document.write(globalNum);
}

printNum();
/* 결과
   undefined
   20
*/

 

처음에 전역변수로 globalNum을 먼저 선언했음에 불구하고!! 첫 번째 변수 호출 값은 undefined로 출력된다.

 

그 이유는 자바스크립트 내부에서 호이스팅에 의해 다음과 같이 코드가 변경되어 처리되기 때문이다.

var globalNum = 10;

function printNum() {
    var globalNum; // 호이스팅에 의해 변수의 선언 부분이 함수의 맨 처음 부분으로 이동되고 초기화도 완료 (undefined)
    document.write(globalNum); // 참조 값이 undefined임
    globalNum = 20;
    document.write(globalNum); // 20으로 할당 이후 출력, 20
}

printNum();

 

예제를 보면 할 수 있듯, var는 "선언 + 초기화"가 한 번에 일어난다. 즉, 스코프가 생성될 때 이미 변수는 undefined 상태로 만들어진다.

 

호이스팅 시점에서 이미 초기화(undefined)가 끝나 있기 때문에, ReferenceError가 발생하지 않고 undefined가 출력된다. (모호한 var 자식)

 

반면 let과 const는 다르다.

선언은 호이스팅 되지만, 초기화는 실제 코드 실행 시점에 이루어진다.

console.log(b); // ReferenceError
let b = 20;
console.log(b); // 20


// 1단계: 선언이 호이스팅 됨
// JS 엔진이 b가 존재한다는 사실은 인식함
// 하지만 아직 초기화되지 않음 -> "TDZ(Temporal Dead Zone)" 상태
// 이 구간에서는 접근 불가능!!

console.log(b); // ReferenceError
b = 20;         // 초기화 + 할당이 여기서 동시에 수행
console.log(b); // 20

 

let은 선언은 호이스팅되지만 초기화는 코드 실행 시점에 이루어진다.

초기화 이전의 영역을 일시적 사각지대(TDZ, Temporal Dead Zone) 라고 부르며 TDZ 안에서는 변수를 “인지했지만 접근할 수 없는 상태”라 ReferenceError가 발생한다.

 
console.log(c); // ReferenceError
const c = 30;
console.log(c); // 30


// 1단계: 선언은 호이스팅됨 (let과 동일)
// 2단계: 초기화는 실행 시점에 일어남 (하지만 const는 반드시!! 동시에 할당 필요)

console.log(c); // ReferenceError
const c = 30;   // 선언 + 초기화 + 할당이 동시에 일어남
console.log(c); // 30

 

 

const 역시 TDZ의 영향을 받는다.

그러나 let과 달리 const는 선언과 동시에 반드시 초기화(값 할당) 해야 한다. 선언만 하고 값을 안 주면 SyntaxError가 난다.


정리하자면, let과 const도 스코프의 맨 위로 "끌어올려지긴" 하지만, 초기화 단계가 실행되기 전에는 접근이 불가능하다.

(var의 모호함을 단번에 잡아준다! let이나 const를 쓰면 예상치 못한 동작이 일어나는 경우가 없을 것.)

 

 

 

오케이, 호이스팅이 뭔지는 알겠는데 그래서? 🤔

 

이전 글에서 스코프와 변수에 대해 설명하다가 갑자기 웬 변수가 위로 끌어 올려지는 호이스팅에 대해 설명하는지 의아할 수 있다.

스코프와 호이스팅은 별개의 개념처럼 보이지만, 사실 서로 관련이 있다.

 

스코프는 변수의 유효 범위를 결정하고, 호이스팅은 해당 스코프 내에서 변수 선언이 어떻게 처리될지를 결정한다.
따라서 스코프는 호이스팅이 동작하는 범위를 결정하며, 호이스팅은 특정 스코프 내에서 변수가 언제, 어떻게 접근 가능한지를 결정한다.

 

예를 들어, 

  • 함수 스코프에서의 var 호이스팅
function example() {
  console.log(x);
  var x = 10;
  console.log(x);
}

example();

 

JS 엔진은 코드를 실행하기 전에 스코프(함수 단위) 를 먼저 설정한다.

이때 var 선언은 우리가 배운 듯이 호이스팅 될 텐데,
var는 함수 스코프를 가지기에 코드의 맨 상단이 아니라 함수의 상단으로 끌어올려진다.


그럼, 내부적으로 다음과 같이 인식 된다.

function example() {
  var x; // 선언이 위로 끌어올려짐 (초기화는 아직)
  console.log(x);
  x = 10;
  console.log(x);
}

 

  • 스코프가 다를 때의 호이스팅 비교
var x = 1;

function test() {
  console.log(x); //?
  var x = 2;
  console.log(x); //?
}

test();
console.log(x); //?


// 호이스팅이 된다면?
var x;

function test() {
	var x;
    console.log(x); // undefined
    x = 2;
    console.log(x); // 2
}

x = 1;


test();
console.log(x); // 1

 

 


스코프 체인(Scope Chain)이란?

 

말 그대로 스코프가 계층적으로 연결되어있는 것을 말한다. 함수가 중첩되면서 계층적으로 연결되는 것이다.

즉, 변수를 참조할 때 계층을 따라 아래에서 위로 올라가며 찾는 매커니즘이다.

let x = 30;

funtion scope(){
	let y = x;
    
	return function(){
    	let z = y;
        console.log(z);
    }
}

 

예제를 보면, 스코프가 세 개 정도로 나뉘는데

  • x을 선언한 전역 스코프
  • 함수 scope()의 block scope 1
  • 함수 scope() 안에 리턴되는 함수의 block scope 2 

가장 내부에 위치한 block scope 2의 지역 변수는 y와 x를 참조할 수 있고 block scope 1는 x를 참조할 수 있지만,

그 반대로 전역 스코프에서는 y와 z를 참조할 수 없고, block scope 1에서는 z를 참조할 수 없다.

 

즉, 한 마디로 안쪽 함수는 바깥 스코프를 볼 수 있지만, 바깥 함수는 안쪽 스코프를 볼 수 없다.

 

var 동 = '전역에 존재하는 동';
var outer = function(){ // outer scope
	var 훈 = 'outer 함수 지역에 존재하는 훈';
    console.log(동); // '전역에 존재하는 동' 출력
    console.log(훈); // 'outer 함수 지역에 존재하는 훈'출력
    
    var inner = function() { // inner scope
    	var 동 = 'inner 함수 지역에 존재하는 동';
        
        console.log(동); // 'inner 함수 지역에 존재하는 동' 출력 -> 전역, inner scope 모두에 동일 변수가 있기에 더 가까운 스코프의 동 출력
        console.log(훈); // 'outer 함수 지역에 존재하는 훈' 출력
    }
    inner();
}  
outer();
console.log(동); // '전역에 존재하는 동' 출력
console.log(훈); // ReferenceError 발생

 

두 번째 예제 또한, 스코프를 세 개로 나눌 수 있다. 

  • 전역 스코프
  • outer 스코프
  • inner 스코프

먼저 outer 함수가 호출되어, outer 함수 내부의 console.log 문들이 실행된다.

이때 outer 함수는 변수 동과 훈을 참조해야 하는데, 두 변수를 찾을 때 현재 스코프에서 시작하여 바깥(상위) 스코프로 단계적으로 올라가며 탐색한다.

 

outer 스코프 안에는 훈이 선언되어 있으므로 같은 스코프의 변수를 그대로 사용하고, 동은 outer 스코프에 없으므로 한 단계 위의 전역 스코프에서 동을 찾아 값을 참조한다.

 

그 다음 inner 함수가 실행된다. inner 함수 역시 변수 동과 훈을 참조하기 위해 현재 스코프에서 바깥으로 차례로 탐색한다.

이때 동은 inner 함수 내부에서 새로 선언된 지역 변수이므로 전역 스코프의 동보다 가까운 스코프에 존재하는 inner의 동을 우선 참조한다. 따라서 console.log(동)은 inner 함수 지역에 존재하는 동을 출력한다.

 

반면 훈은 inner 스코프에는 존재하지 않지만, 상위 스코프인 outer에 선언되어 있으므로 outer의 변수를 찾아 사용한다.

 

마지막으로 전역 스코프의 console.log가 실행된다. 전역에는 변수 동만 존재하므로 console.log(동)은 정상적으로 출력되지만, 전역에는 훈이 없기 때문에 console.log(훈)에서는 ReferenceError가 발생한다.

 

 

+) 보너스...

스코프 체인의 예제가 var 범벅인데 호이스팅 문제가 왜 안 나지??

 

위의 스코프 체인의 동훈이 예제를 실제 어떻게 인식되는지 적어보자면,

// 전역 스코프
var 동;
var outer;

동 = '전역에 존재하는 동';

outer = function() {
  var 훈;
  var inner;
  
  훈 = 'outer 함수 지역에 존재하는 훈';
  console.log(동);  // 전역의 동 참조 (현재 스코프엔 없음)
  console.log(훈);  // 'outer 함수 지역에 존재하는 훈'

  inner = function() {
    var 동;
    동 = 'inner 함수 지역에 존재하는 동';
    console.log(동); // 'inner 함수 지역에 존재하는 동'
    console.log(훈); // 상위 스코프(outer)의 훈 참조
  }
  
  inner();
}

outer();

console.log(동); // '전역에 존재하는 동'
console.log(훈); // ReferenceError

 

참조 에러는 같은 스코프 내에 선언이 존재하기만 하면 호이스팅 되어도  ReferenceError는 발생하지 않는다.
다만, 값이 아직 할당되지 않았다면 undefined가 출력될 뿐이다.

 

위에서 undefined가 출력되지 않은 이유는, 호이스팅 되고 난 뒤에 할당이 제대로 들어가고 나서 출력을 했기 때문이다.
위의 스코프로 변수를 찾으러 가도 콘솔 출력 전에 값이 이미 할당이 된 상태에서 출력을 하기 때문이다.

 

반면 전역 스코프에서의 훈 출력은 전역 스코프 내에서 선언조차 되지 않았기에 참조 에러가 뜬다.

 

@

@

@

 

 

💬 다음 글은 준비 중...


참고자료 모음집

 

모던 자바스크립트 딥 다이브

호이스팅(되게 자세히)

호이스팅이 무엇인가?!

호이스팅이란? 완벽하게 이해하기

스코프와 스코프 체인 | JavaScript Basics

스코프, 스코프 체인

'JS' 카테고리의 다른 글

this에 대해서  (0) 2025.11.11
실행 컨텍스트 보충 (전역 객체와 전역 코드 평가)  (0) 2025.11.09
실행 컨텍스트란?  (0) 2025.11.03
JavaScript의 Scope에 대해서 (3)  (0) 2025.11.03
JavaScript의 Scope에 대해서 (1)  (0) 2025.10.26