JavaScript 이벤트 루프, 호이스팅, 클로저 개념 요약 정리

작성 : 2023-09-05수정 : 2023-10-04

목차 펼치기


이벤트 루프(Event Loop)

이벤트 루프는

싱글 스레드인 JavaScript 엔진과 멀티 스레드인 구동 환경을 연결시켜주는 역할

을 한다.


JavaScript의 엔진은 Call Stack과 Memory Heap으로 이루어져있다. 단일 호출 스택을 사용하고 있기 때문에 싱글 스레드다. JavaScript가 구동되는 브라우저나 Node.js 등의 환경은 멀티 스레드다.



동기적 함수들은 Call Stack에서 처리된다. 하지만 Promise, DOM, Timeout과 같은 비동기 함수들은 브라우저나 런타임 환경에서 지원하는 Web API를 호출해서 사용한다. Web API는 Callback Queue에 추가한 후 Call Stack이 비게 되면, 우선 순위에 따른 Callback Queue에서 FIFO 방식으로 Task를 Call Stack에 푸시하는 게 이벤트 루프의 역할이다.


Callback Queue의 세 가지 종류


참고

호이스팅(Hoisting)

Hoist란 ‘끌어올리다’라는 뜻으로, 호이스팅은 스코프 단위로 변수와 함수의 선언과 할당이 분리되어

식별자가 우선 선언 된 것 같은 현상을 추상화한 개념

이다. 선언은 JavaScript 엔진 구동 시 최우선으로 해석되기 때문에 끌어 올려진 것처럼 보일 수 있지만, 할당은 실제 코드가 실행되면서 동작한다.


전역 스코프에서는 전역 변수의 호이스팅이, 지역 스코프에서는 지역 변수의 호이스팅이 발생한다.


var

키워드로 변수를 선언할 경우에는 호이스팅 시 선언과 초기화 단계가 동시에 진행된다. 즉, 선언 지점에서 값이 할당되기 전에도 메모리에 변수가 저장되어 있기 때문에 어디서든 참조할 수 있다는 뜻이다. 이 때 변수는

undefined

값을 반환한다.


const

,

let

키워드로 변수를 선언할 경우에는 호이스팅 시 선언 단계만 진행된다. 즉, 선언 지점에서 초기화와 할당을 진행하기 때문에 선언 지점보다 먼저 사용할 경우 메모리가 할당되어 있지 않아 참조 오류가 발생한다는 뜻이다. 스코프의 시작 지점부터 이 초기화 지점까지의 구간을 ‘일시적 사각지대(TDZ : Temporal Dead Zone)’라고 한다.


function

키워드를 사용해 함수를 선언할 경우에는 함수 전체가 호이스팅 되기 때문에 어디서든 호출할 수 있다.



함수를 정의하는 방식에 따른 호이스팅

함수 선언문의 경우 호이스팅으로 인해 함수가 잘못 오버라이딩 될 경우, 디버깅이 어려운 비즈니스 오류가 발생할 수 있다.


  1. 함수 선언문

    • 함수 전체가 호이스팅 된다.

    javascript
    1function foo() { ... }
  2. 익명 함수 표현식

    • 변수만 호이스팅 되고, 함수는 실제 선언부에서 할당된다.

    • 예전에는 함수 디버깅 tracing 시 이름이 나오지 않았지만, 이제는 모든 브라우저에서 이름을 연결지어준다.

    javascript
    1var foo = function() { ... }
  3. 기명 함수 표현식

    • 변수만 호이스팅 되고, 함수는 실제 선언부에서 할당된다.

    • 어차피 외부에서 함수명으로 접근할 수 없으나, 예전에는 디버깅 tracing을 위해 사용했다.

    javascript
    1var foo = function bar() { ... }

참고

클로저(Closure)

함수와 함수가 생성될 때 렉시컬 환경의 조합

이다. 모든 함수는

[[Environment]]

라는 숨김 객체에 함수를 생성할 때의 환경, 즉 해당 함수의 상위 렉시컬 환경에 대한 참조를 저장한다. 이를 통해 함수는 호출부와 상관없이 자신이 생성된 환경을 기억할 수 있게 되는데, 생성된 후에는 변하지 않는다.

JavaScript는 함수를 어디서 호출했는 지가 아니라 함수를 어디에 정의했는 지에 따라 상위 스코프를 결정하는 렉시컬 스코프(정적 스코프)를 따르기 때문에 발생한다.


A 함수 내에서 B 라는 중첩 함수(nested function)를 선언했을 때, B 함수는 A 함수의 렉시컬 환경을 기억하여 B 함수는 A 함수의 스코프를 접근해 사용할 수 있다. 이 때 A 함수의 실행이 종료되어 실행 컨텍스트에서 제거되어도, B 함수가 참조하고 있는 A 함수의 변수는 제거되지 않는다.

이론상으로는 JavaScript의 모든 함수는 상위 스코프를 기억하기 때문에 클로저지만, 일반적으로는

상위 스코프의 식별자에 접근하고, 외부 함수보다 더 오래 유지되는 중첩 함수

를 클로저라고 한다.

브라우저 자체에서 메모리 최적화를 통해 중첩 함수에서 참조하는 상위 스코프의 식별자만 기억하며, 이 변수를 자유 변수(free variable)라고 한다. 즉, 클로저란 “자유 변수에 묶여있는 함수”다.

State를 은닉하고, 접근을 제어해 안전하게 변경하고 유지하기 위해 사용한다.

단,

new Function

문법을 사용하며 생성한 함수의 경우 현재의 렉시컬 환경이 아닌, 전역 렉시컬 환경을 참조하게 된다.


참고

Wanna get in touch?

All Icons byiconiFy