[JS/호이스팅/실행 컨텍스트/이벤트 루프] JS 코드의 흐름
저는 var을 한번도 써본적이 없지만, JS는 오래동안 써왔던 언어이므로 ,var를 마주칠 가능성이 높습니다.
(var은 호이스팅을 하기 때문에, 선언하지 않아도 접근이 가능해지며 재할당을 해도 아무런 상관이 없는 문제가 있었습니다)
JS의 코드의 흐름중에 하나인, 호이스팅부터 짚고 넘어가볼까요!
호이스팅(Hoisting)
아래의 코드를 보시죠! 어떤 문제가 있어보이나요?
var a=5
console.log(a)
아뇨, 없습니다! a=5 라는 결과가 나올겁니다. 아래의 코드를 보시죠 . 문제가 있을것 같나요?
console.log(b)
var b=5
놀랍게도, 아래의 코드도 "실행"은 됩니다. 의도치 않은 결과인 "undefined"를 뱉을 뿐, 에러는 발생하지 않습니다.
C언어,Java와 같은 정적인 언어를 자주 접해왔던 저는 처음에는 "뭐지 , 이 무근본은 ..?"이라고 생각했습니다.
선언을 하지도 않았는데, 이걸 에러를 안뱉어..? 그리고, 더 놀라웠던건, 같은 이름의 변수를 또 "선언"할 수 있었다.
var a =5
console.log(a)
var a=7
console.log(a)
하지만, 이러한 로직이 가능한건 , 분명한 이유가 있을거고, 바로 그 개념이 "호이스팅"의 개념입니다.
호이스팅은 이렇게 동작합니다. 코드를 실행하기 전에, V8엔진 형님이 먼저 코드를 한번와서 쓱 훑고 , "선언"된 변수들을 미리 적어갑니다. 코드 상의 동작으로 따지면, 아래와 같이 움직입니다. a를 최상단으로 옮기는거죠.
따라서, console.log(a)에 undefined가 나오게 되는겁니다. 아직 "값"자체는 할당되지 않았으니까요!
console.log(a)
var a=5
//======== this code convert to below =====
var a
console.log(a)
a=5
새로나온 let, const도 호이스팅을 안할까요? 아닙니다. 합니다! 단 , 값이 "할당"되기전에 접근하는걸 막는겁니다.
console.log(b)
let b =5
// convert code to below
let b
console.log(b)
//TDZ , 할당되기 전까지 b로의 접근을 막는다. b에 접근하면 error!
b=5
Summary
- 호이스팅 문제로 인해서, var은 더 이상 쓰지않는다. 하지만, 원시코드 형태로 만나볼 수 있으니 알고는 있자.
- 호이스팅은 V8엔진이 변수를 미리 기억하는 과정이다. 이 현상 때문에, 코드의 흐름상 제일 위로 끌어올리는 것과 같은 현상이 되는거다. 이때, var은 undefined로 되고, let,const는 TDZ를 만들어 할당이 되지 않았다면 접근을 막게된다.
Scope
C언어/Java와 같은 정적인 언어를 사용해봤다면, 혹은 Python을 다루어 보았다면 프로그래밍 언어는 "실행블록"이라는 개념이 있습니다. 예를들어, C언어,java는 중괄호 , python은 콜론(:) + 스페이스바 4칸(Tab)으로 구분을 합니다.
1개의 실행블록은, 독립적인 공간처럼 사용할 수 있어서, 외부에서 내부로 접근을 못합니다. 아래와 같이 말입니다.
void printNumber(){
int num=5
printf("%d",num)
}
printf("%d",num) //error!
실행블록과 같은 논리적인 범위, 변수값들의 유효범위를 JS에서는 Scope라고합니다. var은 이 scope가 애매합니다.
즉 , 지역변수와 전역변수가 애매해진다는거죠. 정확히는 var은 함수내부에서는 , 지역변수입니다만, if,for문과 같은 곳에서는 전역변수처럼 동작하게 됩니다. 이걸 JS에서는 "함수스코프를 가졌다"고 합니다.
for(var i=0;i<5;i++){
console.log("inner",i)
// 1,2,3,4,
}
console.log("outer",i)
// boom! 5
function sayHi(){
var say="hello"
}
console.log(say)
//error!
let,const에서 오면서 이걸 고쳤죠! 다른 언어와 마찬가지로 , blockScope로요!
정리하자면, Scope란 , 변수 이름, 함수 이름, 클래스 이름과 같은 "식별자(Identifier)"가 본인이 선언된 위치에 따라, 다른 코드에서 자신이 참조 될 수 있을지 없을지 결정 되는걸 이야기 합니다. 식별자의 유효범위인 셈이죠.
이벤트 루프
Chrome을 간소화 시키면 아래와 같은 구조를 띄게 됩니다. 실행 컨텍스트는 간단하게 이야기하면, "call stack에 쌓이는 로직들에 대한 모든 내용" 입니다. 우리가 실행하는 함수나 로직들은 call stack에 순차적으로 하나씩 쌓이게 됩니다. (즉, 들어온대로 처리가 된다는거죠!)
자세하게 들어가면, 전역 컨텍스트 , 함수 컨텍스트 , 지역 컨텍스트 등등 다양하게 있고, lexical scope chain ,this 등이 context마다 있습니다만, Event Loop에서는 "callstack에 쌓이는 로직" 이정도로 이해해도 큰 문제는 없을겁니다.
아래의 코드가 JS에서 실행된다고 생각해보죠.
console.log("welcome"!);
function func1(){
console.log("Hello!");
}
setTimeout(func1,5000)
0. 메모리 영역에 func1을 등록해놓습니다.
1. console.log()가 callstack에 쌓입니다.
2. console.log() 실행이 끝나고, setTimeout이 실행됩니다. setTimeout은 실행됬다가, 비동기함수이므로 WebAPI에게 callback을 던집니다. 여기서는 func1을 던지겠네요!
3.Timer는 setTimeout이 요청했던대로, 5초뒤에 Task Queue에 callback을 던집니다.
4. 이제 더 이상 실행할게 없으므로, call stack에서 annoymous(전역객체)가 마지막으로 나갑니다. event loop는 callstack이 완전이 비어질 때까지 기다립니다.
5. Event loop는 call stack이 완전히 비어있고, Task Queue에 작업이 남아있다면, 그 작업을 call stack으로 올려줍니다.
6. callstack에서 callback 함수가 실행되고, 이제 끗! 입니다.
Eventloop는 , call stack을 항상 지켜보다가 , task queue에 남은 작업이 있다면, call stack으로 올려주는 역할을 하게됩니다. 예를들어, 아래의 코드는 bar을 즉시 반환할까요? 아닙니다. 콘솔에는 foo bar이 찍힙니다. 이게 자바스크립트의 코드 흐름 때문에 그렇습니다!
function func1(){
console.log("bar")
}
setTimeout(func1,0)
console.log("foo")
다음시간에는 실행컨텍스트를 자세하게 들여다보면서, scope chain을 따라서 "this"가 결정되는 과정을 보고, closure패턴을 한번 봅시다! 왜 "this"가 함수 호출시에 결정되는지를 알려면, 실행컨텍스트를 보면됩니다!
실행 컨텍스트를 이해하려면, 오늘 적은 스코프,호이스팅,이벤트루프 ,call stack에 대한 개념을 조금 아셔야합니다.
실행 컨텍스트가 끝나면 , promise를 포스팅해서 ! 드디어, AJAX 즉 , 웹서버와 통신을 비동기적으로 하는걸 봅시다!
refer