hoisting

2025. 5. 8. 08:59Language/JavaScript

let

  • 변수의 scope가 선언된 블록({})으로 한정되며, 동일한 이름의 변수 재선언 불가.
  • var 변수처럼 호이스팅이 발생하지만, 초기화 전까지 사용할 수 없음.
  • const : let의 속성 + 불변성

 

hoisting

  • 자바스크립트 엔진이 각 스코프의 실행 전에 선언을 미리 처리.
  • 코드 실행 전에 변수, 함수, 클래스, import 선언문이 해당 스코프의 맨 위로 끌어올려진 것처럼 동작.
  • var : 선언만 호이스팅 되고, 값 할당은 호이스팅되지 않아 선언 전에 접근하면 undefined 출력.
  • TDZ(Temporal Dead Zone) : let, const로 선언한 변수는 호이스팅은 되지만, 초기화 전에 접근할 수 없어 ReferenceError가 발생하는 구간.
  • 함수 : 전체가 호이스팅되어, 선언 전에 호출해도 정상 동작.
  • 함수 표현식, 클래스 선언 : 호이스팅되지만, 초기화 전에 사용 불가.

 

함수 선언문

  • 작성 형태 : function foo() {...}
  • 전체 호이스팅으로, 선언 전 호출도 정상 동작.
  • 전역 함수나 재사용 함수에 사용.

 

함수 표현식

  • 작성 형태 : (const/var/let) foo = function() {...}
  • 변수에 함수를 할당하는 방식.
  • 변수 선언만 호이스팅 되어, 코드 실행 시 함수가 할당되므로 선언 이후에만 사용 가능.
  • 콜백 함수, 일회성 함수, 클로저 등에 사용.

 

예제에서 보는 내 생각의 흐름

예제1

let x = 1;
function scopeTest(){
	console.log(x); // 출력1
    if(true){
    	let x = 2;
        console.log(x); // 출력2
    }
    console.log(x); // 출력3
}
scopeTest();
console.log(x); // 출력4

//1
//2
//1
//1

여기서 총 3개의 scope가 있다.

scope1 : 기본 scope_let x=1, scopeTest()

scope2 : scopeTest() 내부_if(true)

scope3 : scopeTest() 내부의 if(true)_let x=2

 

출력1 :  scope1의 x

출력2 :  scope3의 x

출력3 : scope1의 x

출력4 : scope1의 x

 

scope1의 x를 x1, scope3의 x를 x2로 하자.
x1을 scope2에서 사용할 수 있다면, scope2에 x가 선언되어 있다고 본다.
scope2에서 x를 사용할 수 있다면, scope3에도 사용할 수 있을 것이다.
scope3에 이미 x가 있는데 x2로 재선언한다면, let은 동명의 변수 재선언이 불가하다는 내용에 위배되는 것 같다.
뭐지?
x1은 전역변수, x2는 지역변수인가?

 

 

예제2

var a = "j";
function testHoisting(){
	console.log(a);
    if(!a){
    	var a = "y";
        console.log(a);
    }
    console.log(a);
}
testHoisting();
console.log(a);

//undefined
//y
//y
//j

여기도 총 3개의 scope가 있다.

scope1 : 기본 scope_var a="j", testHoisting()

scope2 : scopeTest() 내부_if(!a)

scope3 : scopeTest() 내부의 if(true)_var a="y"

출력 1, 2, 3은 scope3의 a, 출력 4는 scope1의 a이다.

 

scope2, 3은 사실 분리된 게 아니라 하나의 scope일 수도 있을 것 같다.(총 3개가 아닌 2개의 scope)
하나의 scope라면 if문과 else문에 var a="y"를 각각 선언했을 때, 하나의 scope 안에서 동명의 변수를 두 번 선언하는 것인데, 이것은 재선언인가?
if-else는 항상 둘 중 한 번만 실행되기 때문에 재선언은 아닐 것 같다.

예제1에서는 출력1이 scope1의 변수를 출력했는데, 어째서 여기서는 scope3의 변수를 사용할까.
Error를 최대한 없애기 위해 기본은 해당 scope 내의 변수를 사용하고, Error가 발생한다면 외부 scope의 사용할 만한 동명의 변수를 찾아 사용하는 것인가?
아니다.
예제2의 var a에 대하여 scope2, 3은 하나의 scope인데, var 변수는 let의 블록 scope와 달리 함수 scope만 따르기 때문이다.

 

scope2의 if문의 조건문에 사용된 !a는 scope3의 a로, 선언 전에 호출하여 undefined이고, false이기 때문에 !a는 true로 if문이 실행된다.

 

예제3

let i = 100;
for(let i=0; i<3; i++){
	setTimeout(() => {
    	console.log(i); //출력1
    }, 0);
}
console.log(i); //출력2

//100
//0
//1
//2

setTimeout() : 지정한 시간이 지난 뒤 함수를 한 번 실행하도록 예약하는 비동기 타이머 함수.

setTimeout()의 지연 시간이 0이어도 매크로태스크 큐에 등록되어 동기 코드인 출력2를 실행한 뒤 실행된다.

비동기 방식에 의해 출력1이 출력2 뒤로 미뤄졌는데, 어째서 333이 아닌 012를 출력하는 것일까? for문이 한 번 돌 때마다 변수 i를 새로 선언하는 건가?

 

예제4

var i = 100;
for(var i=0; i<3; i++){
	setTimeout(() => {
    	console.log(i); //출력1
    }, 0);
}
console.log(i); //출력2

//3
//3
//3
//3

당황하지 말자.

var는 재선언이 가능하다.

i가 100이었지만 for문 내에서 i는 0으로 재선언되어 3까지 증가했다.

i가 0부터 3으로 바뀌는 동안 비동기 함수인 setTimeout은 뒤로 미루고, 출력2에서 3이 된 i를 출력한다.

동기 코드가 모두 실행되었으므로 매크로태스크 큐에서 출력1을 꺼내 3이 된 i를 세 번 출력한다.

 

당황하지 않는 것을 실패했다.

012와 333의 차이. 무엇인가.

클로저를 알면 해결이 될 수 있을 것 같다는 제보를 받았다.

 

 

Closure

내부 함수가 외부 함수의 변수(지역 변수 등)에 계속 접근할 수 있는 현상.

외부 함수가 실행을 마치고 종료된 후에도 내부 함수는 외부 함수의 변수에 접근할 수 있다.

즉, 함수가 자신이 선언될 때의 환경을 기억하는 기능.

function outer() {
  let message = "hello";
  return function inner(name) {
    return message + " " + name;
  }
}
const greet = outer();
console.log(greet("JS")); // "hello JS"

inner 함수가 outer 함수의 지역 변수 message에 접근할 수 있는 현상.

 


클로저로도 아직 명쾌한 답을 알아내지 못했다.

이 문제는 자바스크립트의 실행 순서도 알아야 할 것 같다.

https://jyulog.tistory.com/entry/Event-Loop

 

Event Loop

콜 스택(Call Stack)함수 호출 정보를 쌓아두고, 실행이 끝나면 다시 이전 위치로 돌아갈 수 있게 한다.자바스크립트는 싱글 스레드 언어로, 한 번에 하나의 작업만 처리할 수 있기 때문에 동기적으

jyulog.tistory.com

이제 예제4는 확실히 알았다.

i가 0, 1, 2가 되는 동안 매크로태스크 큐에 작업을 저장하고, i가 3이 된 채로 동기 코드인 출력2 실행.

이후 매크로태스크 큐에서 출력1을 한 개씩 세 번 꺼내 실행한다. 이때, i가 3인 상태이므로 3 출력.

 

이제 예제3을 알아야 한다.

'Language > JavaScript' 카테고리의 다른 글

Event Loop  (1) 2025.05.09
fetch()  (0) 2024.03.11