js 동작방식(+ 엔진 특성)
2022.12.16 테크톡
요약:
개발자가 작성한 스크립트 언어를 실행 가능한 언어로 변형하여 실행, 실행 순서 및 메모리를 관리하는 엔진 필요(여기선 V8)
자바스크립트 런타임: 엔진을 포함한 API 및 기능 집합
자바스크립트 엔진: 좁은 의미로 인터프리팅 역할
⇒ V8 엔진을 기반한 런타임으로 Chrome이 동작한다.
인터프리터 언어
보통의 인터프리터 언어는 한 줄마다 실행되어 같은 변수가 나오더라도 기계어로 번역하여 효율적이지 못함
요즘 인터프리터 언어는 아래의 과정을 거침
(1)JIT 컴파일
(2)수행 과정
엔진 및 런타임
- 자바스크립트 엔진
- Heap: 각 함수 별 선언 및 할당되는 모든 변수 및 함수를 적재하는 메모리 영역
- Call Stack: 함수 실행 순서에 맞게 쌓이는 스택, Heap에 있는 함수에서 필요한 데이터의 포인터를 가지고 있음
- 비동기 지원
- Web APIs: 기본 js에 없는 DOM, ajax, setTimeout 등의 라이브러리 함수들 브라우저나 OS 등에서 다양한 언어로 구현되어 제공 됨
- Callback Queue: Web ApIs 에서 발생한 콜백 함수들 적재
- Event Loop: Callback Queue에 적재된 함수를 Call Stack이 비어있으면 옮김
엔진 실행 과정
(A) Compilation Phase
매 함수 실행 시(첫 함수는 main()) ASTs 생성 및 바이트코드로 변경 JIT 컴파일 기법을 위해 프로파일러로 함수 호출 횟수를 저장/추적.
⇒ 위 과정에서 변수, 함수의 선언을 Heap에 적재
e.g. 메인함수를 예로 들면 전역에 있는 변수, 함수만 적재
(B) Execution Phase
:변수의 할당 / 함수의 호출 및 실행
- 변수 할당
- 이전 컴파일 과정에서 선언된 변수 확인
- 없으면 선언, 할당 동시
- 함수 호출 및 실행
- 이전 컴파일 과정에서 선언된 함수 확인
- Heap에 새 함수를 위한 Local Execution Scope 영역 생성
- Call Stack에 Heap에 대한 포인터를 갖는 함수 적재
- (A)와 (B) 반복
++
JS 엔진 특성
function-level scope: var
js 실행은 함수에 따라 (A)컴파일, (B) 수행이 재귀적으로 이루어진다.
특정 함수 내 var의 선언은 본 함수 (A) 컴파일에 정의되기 때문에
변수 var scope는 function-level이 된다.
ES6에선 if, for 문등의 block-level 단위 변수를 위해 const, let 추가
Scope Chain
(B) 단계에서 변수 할당 시 본 함수의 Heap영역에 선언이 되어있는지 먼저 검사,
없으면 함수가 호출되기 이전의 함수의 스코프로 올라가면서 확인(main까지).
⇒ 각 함수 Heap Scope에 변수 선언 존재 여부를 연쇄적으로 찾기때문에 Scope Chain 이라 부름
Variable Hoisting
(A) 단계에서 선언, (B)단계에서 변수를 할당하기 때문에 같은 function-level 이면 선언과 할당을 나누어 해도 선언이 먼저 된 것으로 처리됨
만약 함수 내 변수가 선언되지 않았다면 Scope Chain을 통해 main()함수까지 올라가면서 변수 선언을 찾는다. main 함수 Heap Scope(Global Scope, window)에도 선언되어있지 않다면 main 함수 영역에 변수를 선언, 전역 변수
Variable Shadowing
특정 함수의 heap scope에 변수가 선언되어 있다면 할당은 해당 Heap scope에 선언된 변수에 할당됨, 이전 함수에서 이름이 같은 변수가 선언되어 있더라도 Scope Chain이 일어나지 않기 때문에 Variable Shadowing이라 부름
Garbage Collection
메모리 청소의 의미로 Garbage Collection
- 함수 직접 수행이 끝나면 Stack 에서 수행 완료된 함수의 정보를 없애면서 Heap 메모리 내 수행 완료된 함수의 Heap Scope 도 없앰
- 마찬가지로 전체 자바스크립트 실행이 끝나게되면 main() 함수의 Global Scope(Window) 도 사라짐
- Reference Count 통해 지우는스위프트 등과 다르게, 자바스크립트는 단순히 함수(포인터)가 '접근 가능한지'를 기반으로 가비지 콜렉션 를 수행
- 함수 수행을 변수에 할당한 경우엔 함수 수행이 끝났다고 하더라도 할당된 변수로 또 함수 수행이 가능하기 때문에 본 함수에 대한 Garbage Collection 를 안하는 경우가 존재하는데 이게 **closure
closure
- 함수 호출을 변수에 할당하게 되면 함수의 호출은 일회성으로 호출이 끝나면 사라지는것이 아니라 변수를 통해서 계속해서 반복 호출이 가능
- f 함수 호출을 위해 생성된 f 함수의 Heap Scope 는 지워질 수 없다
- 조금 쉽게 생각하자면 f 함수 Heap Scope 에는 f 함수 수행을 위해 넘긴 파라미터 값 1 도 들고있기 때문에 Heap Scope 를 Garbage Collection 할 수 없음
- 이처럼 함수 호출을 변수에 할당하게 되면 f 함수의 Heap Scope 와 f 를 호출한 함수의 Heap Scope 가 파라미터 1 을 기준으로 강하게 묶여있음
고로, f 함수 실행이 끝났음에도 불구하고 f 함수의 Heap Scope 가 Garbage Collection 되지 않는다.
Closure 는 함수의 Heap Scope 와 해당 함수를 호출하는 함수의 Heap Scope 를 연결하는것으로, 함수 호출이 끝나더라도 Scope 는 여전히 해당 함수를 호출한 함수의 Scope 에 ‘갇혀있는’ 개념입니다.
참조
호이
https://hanamon.kr/javascript-호이스팅이란-hoisting/
컴파일
https://aaronryu.github.io/2020/08/27/how-does-a-javascript-engine-work/ (많이 참조)
https://hwan-shell.tistory.com/343
인터프리터(v8 ignition)
https://v8.dev/blog/ignition-interpreter
콜스택,힙
https://engineering.huiseoul.com/자바스크립트는-어떻게-작동하는가-엔진-런타임-콜스택-개관-ea47917c8442
eventloop