var
변수에 할당한 함수 표현식은 Global Scope에 호이스팅이 일어나고 동시에undefined
로 초기화된다.function
키워드로 생성한 일반 함수는 Global Scope에 호이스팅 일어나고 동시에 함수의 정의부까지 전부 초기화된다.let
과const
로 선언한 함수들은 Scirpt Scope에 호이스팅 된다. 또한 초기화도 일어나지 않아 디버거 내에서 값이<value unavailable>
로 표시되는 것을 볼 수 있다.
- 각각의 Statement를 실행할 때마다 각각의 변수에 의도한 값으로 초기화된다.
function
키워드로 생성한 일반 함수는 변화가 없다.
- 브라우저 환경에서의 Global Scope는
window
객체를 가리킨다.- 그래서
this.a_var_function
,window.a_var_function
,globalThis.a_var_function
모두 같은 함수를 가리킨다.
this.a_const_function
등의 참조를 반환하면 undefined가 반환된다.- 참조할 때 this 키워드를 붙이면 명시적으로 현재의 this, 즉 Global Scope인 window 객체에서 참조를 찾는다.
- 하지만 Global Scope에서 Script Scope는 접근할 수 없기 때문에 undefined가 반환된다.
this
키워드를 제거하고 참조를 반환하면 정상적으로 값을 반환한다.- Scope를 명시하지 않을 경우 Scope에 상단부터 값을 찾아나간다.
- 여기서는 Script Scope가 Global Scope보다 먼저 있으므로 Script Scope 먼저 조사를 한다.
function a_function_1() {
const constant_of_function_1 = 10;
const a_function_2 = function () {
console.log(constant_of_function_1);
};
a_function_2();
}
a_function_1();
a_function_1
은 내부에 중첩함수로a_function_2
를 갖고,a_function_2
는a_function_1
의 지역변수인constant_of_function_1
변수를 사용한다.
a_function_1
함수 정의
a_function_1
은function
키워드로 선언된 일반함수이므로 Global Scope에 호이스팅 및 즉시 초기화가 완료된다.
a_function_1
실행
a_function_1
가 실행되는 순간 함수 실행 컨텍스트(Functional Execution Context)인 Local Scope가 생성된다. Local Scope가 생성되면서 함수의 지역 변수인constant_of_function_1
과a_function_2
가 호이스팅되어 Local Scope에 등록된다. 하지만const
변수이기 때문에 초기화는 되지 않고<value unavailable>
로 남아있다.
a_function_1
지역 변수 초기화
a_function_1
함수의 지역 변수인constant_of_function_1
과a_function_2
를 초기화하는 statement를 실행하여 초기화한다. Local Scope의 변수들에 값이 할당된 것을 볼 수 있다.
a_function_2
실행
a_function_2
함수가 실행되면서 다시a_function_2
의 함수 실행 컨텍스트인 Local Scope가 만들어진다. 동시에 상위 Lexical Scope인a_function_1
의 Local Scope를 의미하는 Closure Scope가 만들어진다.a_function_2
는a_function_1
의 지역 변수인constant_of_function_1
를 사용하기 때문에 이 변수가 Closure Scope에 초기화된 상태로 등록된다. 이로써a_function_2
는a_function_1
의 지역 변수인constant_of_function_1
를 사용할 수 있게 되었다.
a_function_2
종료
a_function_2
가 종료되면서a_function_2
의 Local Scope가 사라지고, 동시에 Closure Scope도 사라진다.
a_function_1
종료마지막으로
a_function_1
가 종료되면서a_function_1
의 Local Scope도 사라지고, Global Scope만 남게 되었다.
function a_counter() {
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i); // 출력값 4, 4, 4
}, 1000);
}
console.Log("for 루프 종료", i); // 출력값 4
}
a_counter();
정답은 4만 네 번 나온다. 반복문 변수
i
는a_counter
의 Local Scope에 선언되어 있으며, 내부 함수는a_counter
의 Local Scope를 Closure Scope로 갖는다. 따라서 1초가 지난 이후에는 이미i
가 모두 4로 초기화되어 있기 때문에 4만 나오는 것이다.
내부 함수에서는 Local Scope 다음
a_counter
의 Local Scope를 가리키는 Closure Scope를 사용하고, 여기에i
가 정의되어 있다.
function a_counter() {
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i); // 1, 2, 3 출력
}, 1000);
}
console.log("for 루프 종료", i); // 에러 발생
}
a_counter();
let
과const
변수는 Block Scope를 갖는다. 반복문을 반복할 때마다 Block을 새로 생성하고,i
변수는 생성된 Block 내부에 독립적으로 존재한다. 그리고 해당 Block 내부에서console.log(i)
을 실행하기 때문에1, 2, 3
을 정상적으로 출력한다. 하지만i
는 블록 내부에 선언되어 있으므로a_couter
에서 접근할 수 없기 때문에console.log("for 루프 종료", i)
에서 에러가 발생한다.
내부 함수에서는 Local Scope 다음 Block Scope를 사용하고, 여기에
i
가 정의되어 있다. 그런데 Closure Scope가 보이지 않는다. 이유가 무엇일까?
이유는 단순하다. Closure Scope를 사용할 이유가 없기 때문에 굳이 만들지 않도록 V8 엔진이 최적화를 한 것이다.
a_counter
의 지역 변수를 사용하도록 코드를 수정하면 Closure Scope가 생성된다.
function a_counter() {
for (var i = 1; i <= 3; i++) {
(function () {
var clone = i;
setTimeout(function () {
console.log(clone); // 1, 2, 3 출력
}, 1000);
})();
}
console.log("for 루프 종료", i); // 4 출력
}
a_counter();
반복문마다 새로운 함수 실행 컨텍스트가 생성되고, 해당 컨텍스트 내부의
clone
변수에i
값을 복사해서 저장한다.console.log(clone)
을 실행할 때는 즉시 실행 함수를 가리키는 Closure Context의clone
변수를 참조하기 때문에1, 2, 3
이 정상적으로 출력된다. 참고로a_counter
를 가리키는 Closure Context도 생성된다. 즉, Scope Chain은 Local Scope > Closure Scope (즉시 실행함수
) > Closure Scope (a_counter
) > Global Scope 순으로 생성된다.
네, 맞습니다. 호이스팅은 자바스크립트에서 특정한 동작 방식을 설명하는 용어로, 변수와 함수 선언이 해당 스코프의 최상단으로 끌어올려지는 현상을 말합니다. 이 현상은 컴파일러가 코드를 해석하는 단계에서 일어나며, 이 때문에 변수와 함수 선언은 코드 내 어디에서든 접근이 가능합니다.
자바스크립트는 기본적으로 컴파일 단계에서 코드를 두 번 거치는데, 첫 번째 단계에서는 호이스팅이 일어나고, 두 번째 단계에서는 실제 코드 실행이 이루어집니다. 호이스팅이 일어나는 이유는 컴파일러가 스코프 안에 어떤 변수와 함수들이 존재하는지 미리 알아내기 위해서입니다.
이렇게 스코프 안의 변수와 함수들을 미리 등록함으로써, 런타임 중에 변수나 함수를 찾는 시간을 줄일 수 있습니다. 이를 통해 코드의 실행 속도를 향상시킬 수 있으며, 또한 변수나 함수를 선언하기 전에도 참조할 수 있는 유연성을 제공합니다. 그러나 이런 유연성은 예상치 못한 결과를 초래할 수 있으므로, 호이스팅을 이해하고 올바르게 관리하는 것이 중요합니다.