일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- tanstack query
- 스나이퍼팩토리
- TypeScript
- 프론트엔드
- frontend
- sucoding
- 프로젝트캠프
- 공식문서
- React Query
- 개발
- 유데미
- 리액트프로젝트
- STATE
- Server State
- 상태 관리 라이브러리
- 웅진씽크빅
- 수코딩
- React
- 프론트엔드 개발
- Today
- Total
yunicornlab
JavaScript의 특징 - 인터프리터 언어 본문
자바스크립트 언어의 대표적인 특징 중 하나인 "인터프리터 언어"라는 것에 대해서 알아보자.
컴파일러 언어 vs. 인터프리터 언어
컴파일러 언어는 코드가 실행되기 전 단계인 컴파일 타임에 소스 코드 전체를 한 번에 기계어로 변환한 후에 실행하는 언어이고,
인터프리터 언어는 코드가 실행되는 단계인 런타임에 문 단위로 한 줄씩 중간 코드인 바이트코드로 변환한 후에 실행하는 언어이다.
컴파일러 언어는 컴파일 단계와 실행 단계가 분리되어 있고, 컴파일은 한 번만 수행되므로 실행 속도가 빠르다.
인터프리터 언어는 인터프리트 단계와 실행 단계가 분리되어있지 않고, 코드가 실행될때마다 한 줄씩 인터프리트 과정이 수행되므로 코드 실행 속도가 비교적 느리다.
컴파일 타임? 런타임?
그런데 컴파일 타임은 뭐고 런타임은 뭘까?
C언어를 예시로 들면, 작성한 코드를 저장한 후에 컴파일 명령을 실행하게 되면 자바의 컴파일러가 기계어를 생성해 컴파일하게 되는데, 이 과정이 컴파일 타임이다. (소스 코드 작성 -> 저장 -> 컴파일 명령 실행 -> 구문 분석 -> 코드 최적화 -> 코드 변환 -> 실행 파일 생성)
이후 이 생성된 파일을 실행하는 코드를 작성하면 컴파일러가 이를 해석해서 코드를 실행하게 되는데, 이 과정이 런타임이다. (실행 파일 실행 -> 프로그램 동작 -> 결과 출력)
컴파일러 언어는 이렇게 컴파일 타임이 따로 분리되어 있어서 컴파일 명령어도 따로 존재하지만, 인터프리터 언어는 컴파일 명령어가 따로 존재하지 않는다.
그런데, 막상 실제 프로그래밍 언어를 예시로 컴파일 타임과 런타임을 알아보다보니 오히려 헷갈리는 점이 더 생겼다.
하나하나 정리하면서 이해해보려고 한다.
1) 바이트코드? 중간언어? 기계어?
먼저 중간 언어는 소스 코드가 최종적으로 기계어로 변환되기 전의 중간 단계에서 사용하는 언어로, 바이트코드도 중간 언어 중 하나이다.
중간 언어는 이름 자체에도 뜻이 있듯이, 저수준 언어와 고수준 언어의 중간인 언어를 말한다.
중간 언어인 바이트코드는 고수준 언어로 작성된 소스 코드를 중간 단계로 컴파일한 결과물로, JVM에서 실행되는 Java 바이트코드(.class 파일)와 Python 인터프리터에서 실행되는 Python 바이트코드(.pyc 파일)가 있다.
헷갈렸던 점은, 컴파일러 언어 중에도 컴파일러를 통해 컴파일된 후의 결과물이 다르다는 것이었다.
C언어는 위에서 작성한 것처럼, 컴파일 후에 기게어로 변환된 실행 파일이 생성되지만,
Java는 자바 컴파일러(javac)에 의해 바이트코드(.class 파일), 즉 중간 언어로 컴파일된다.
C# 코드도 .NET 컴파일러에 의해 CIL(공통 중간 언어)로 컴파일된다.
결과적으로, 컴파일러 언어는 컴파일 명령어를 통해 중간 언어나 기계어로 변환된 파일이 생성되는, 즉 컴파일되는 단계가 명시적으로 존재하고, 런타임에서는 생성된 파일을 실행하게 된다.
따라서, 컴파일러 언어가 반드시 "기계어"로 변환시킨다는 오해를 하면 안된다.
현재 언어의 수준보다 낮은 수준으로 낮추는 과정이라고 생각해야 혼동이 오지 않는다.
2) 컴파일이 수행되는 인터프리터 언어
인터프리터는 컴파일 과정이 없다라고 생각하고 인터프리터 언어 예시를 알아보는데, Python도 JavaScript도 컴파일 과정이 있다는 것을 보게되어서 또 혼란이 왔다.
Ruby 언어는 원래 이해하고 있던 인터프리터 언어처럼 동작한다. 즉, 컴파일 타임이 따로 명시되어있지 않고, 코드를 실행하는 런타임에 인터프리터가 코드를 해석하고 코드를 순차적으로 실행하게 된다.
그런데..!! 파이썬은 인터프리터가 컴파일러와 VM(가상 머신)으로 이루어져 있다.
코드를 저장하고 파이썬 파일을 실행하게 되면, 사용자가 컴파일을 따로 수행하디 않아도 암묵적으로 컴파일이 먼저 일어나 바이트코드로 변환된다.
이후 최종적으로 VM(가상 머신이) 바이트코드로 변환된 코드를 한 줄씩 기계어로 변환하며 코드를 실행시키기 때문에 결론적으로 인터프리터 언어라고 불린다.
내부적으로 컴파일 단계가 있지만, 사용자가 직접 수행하지 않으며, 결국은 한 줄씩 인터프리터가 코드를 실행시키기 때문에 인터프리터 언어라고 할 수 있는 것이다.
그렇다면 자바스크립트는 어떻게 되는 걸까?
자바스크립트는 V8 엔진을 통해서 JIT(Just-In-Time) 컴파일러를 사용하게 된다.
JIT(Just-In-Time) 컴파일러는 프로그램 실행 중간에 코드의 일부를 기계어로 컴파일하기 때문에 인터프리터 언어의 유연성과 컴파일러 언어의 성능을 동시에 얻을 수 있다.
JIT 컴파일러의 동작 방식은 다음과 같다.
1) 소스 코드가 실행되면 자바스크립트 엔진이 구문 분석을 통해 추상 구문 트리(AST)를 생성한다.
2) 추상 구문 트리(AST)가 바이트코드로 변환된다.
3) 인터프리터가 변환된 바이트코드를 해석해 한 줄씩 실행한다.
이 때, 함수 호출 빈도나 변수 타입 등의 프로파일링 정보를 수집해 자주 실행되는 코드를 식별하고,
이를 컴파일러에게 전달한다.
4) 이렇게 수집된 자주 실행되는 코드는 JIT 컴파일러가 기계어로 컴파일해서 성능을 최적화한다.
그리고 원래 있던 코드와 최적화된 코드를 바꾼다.
5) 생성된 네이티브 코드를 캐시에 저장해서 재사용하게 되고, 최적화는 지속적으로 수행되면서 성능이 향상된다.
그럼 왜, 이런 컴파일 과정을 중간에 넣었을까?
가장 큰 이유는 속도 때문이다. 인터프리터 언어와 컴파일러 언어의 가장 큰 차이점 중에 속도 측면이 있다.
컴파일러 언어가 기본적으로 인터프리터 언어보다 빠르므로 인터프리터 언어에도 이러한 과정을 통해 성능 이점을 챙긴 것이다.
자바스크립트가 인터프리터 언어로 불리는 이유
자바스크립트 실행에도 컴파일 과정이 있지만, 그럼에도 인터프리터 언어라고 불린다.
그 이유는 결론적으로, 코드를 실행하기 위해 사용자가 따로 컴파일 작업을 수행하지 않기 때문이다.
명시적으로 컴파일을 수행하지 않아도 실행 과정에서 컴파일이 자동으로 처리되어 코드를 즉시 실행할 수 있기 때문에 인터프리터 언어로 분류된다.
물론, 동적 타이핑과 같은 인터프리터 언어의 특성을 가지고있기 때문이기도 하다.
JIT 컴파일러를 사용하는 자바스크립트 엔진으로는, 대표적으로 크롬과 node.js에서 사용하는 V8 엔진이 있다.
자바스크립트 엔진 종류
대표적으로 V8 엔진이 있다.
2008년, 구글에서 개발했고, 현재 크롬과 Node.js에서 사용하고 있다.
애플이 개발한 JavaScriptCore 엔진도 있다. Safari에서 사용하고 있다.
모질라에서 개발한 SpiderMonkey 엔진도 있다. Mozilla Firefox에서 사용하고 있다.
Plus) 컴파일 에러 vs. 런타임 에러
컴파일 에러는 코드가 컴파일되는 동안 발생하는 오류로, 문법적인 오류나 타입 오류 등을 발견할 때 발생한다.
컴파일 에러는 프로그램이 실행되기 전에 발견되기 때문에, 프로그램 개발시 더 안전하다.
런타임 에러는 프로그램이 실행되는 동안 발생하는 오류로, 컴파일 단계에서는 발견되지 않고 프로그램이 실행되는 과정에서 발생하기 때문에 예기치 않은 상황이 많이 발생한다. 이를 방지하기 위해 예외 처리 등을 추가하게 된다.
에러 관점에서 본다면,
컴파일러 언어는 사전에 오류를 발견할 수 있다는 장점이 있고, 인터프리터 언어는 프로그램이 실행되기 전까지 오류를 알 수 없다는 단점이 있다.
하지만, 컴파일 이후 변환된 코드는 이해하기 어렵기 때문에 컴파일러 언어는 디버깅이 어렵지만, 인터프리터 언어는 실행 중에 발생하는 오류를 바로 확인하고 수정할 수 있다.
추가 질문) 타입스크립트는 컴파일러 언어일까, 인터프리터 언어일까?
결론부터 말하면, 타입스크립트는 컴파일러 언어이다!
사실 생각해보면 당연하다.
TypeScript 코드는 실행되기 전에 자바스크립트 코드로 컴파일되어야하기 때문이다.
타입스크립트 컴파일러가 타입스크립트 코드를 자바스크립트 코드로 변환하면서, 타입 오류와 같은 컴파일 타임 오류를 발견하게 해준다.
그리고 인터프리터 언어의 특성인 동적 타입과 달리, 타입스크립트는 정적 타입 언어이다.
참고하기 좋은 추가 블로그 글
https://evan-moon.github.io/2019/06/28/v8-analysis/
V8 엔진은 어떻게 내 코드를 실행하는 걸까?
이번 포스팅에서는 구글의 V8 엔진이 어떤 방식으로 자바스크립트를 해석하고 실행하는지 살펴 보는지에 대해 포스팅하려고 한다. 은 로 작성되었지만 필자의 메인 언어가 이 아니기도 하고, 워
evan-moon.github.io
'Frontend > JavaScript' 카테고리의 다른 글
JavaScript의 핵심! 실행 컨텍스트 이해하기 (0) | 2024.08.07 |
---|---|
JavaScript의 특징 - 멀티 패러다임 언어 (2) | 2024.07.22 |
JavaScript의 특징 - 동적 타입 언어 (0) | 2024.07.17 |
JavaScript의 특징 - 싱글 스레드 언어 (0) | 2024.07.15 |
[JavaScript] 자바스크립트 sort() 함수 이해하고 사용하기 (0) | 2024.06.11 |