자바스크립트는 스크립트 언어

woolee의 기록보관소·2023년 1월 12일
0

FE개념정리

목록 보기
32/35

자바스크립트는 스크립트 언어

JavaScript는 스크립트 언어(가벼운 프로그래밍 언어)이며, 웹페이지를 interactive하게 만드는 데 사용된다. 브라우저 언어라고도 불린다. 자바스크립트를 사용하면 HTML에 동적 텍스트를 삽입할 수 있다.

  • JS는 초기에 DOM을 조작하기 위해 만들어졌다. 초기 웹사이트들이 대부분 정적이었다면, JS 등장 이후 동적 웹사이트가 활성화되었다.

  • JS의 함수는 객체이다. 다른 객체와 마찬가지로 속성(properties)과 메서드를 가지며, 다른 함수에서 인수로 전달할 수 도 있다.

  • JS는 컴파일러를 필요로 하지 않는다. JS는 텍스트로 작성되므로 해석(interprete)만 하면된다. 반면 Java는 컴파일해야 한다.

  • JS는 객체 기반의 스크립트 언어(object-based scripting language)이다. JS의 객체는 프로토타입을 기반하고 있다.
    ⇒ 반면, 자바(Java)는 객체 지향 프로그래밍 언어(object-oriented programming language)이다. 자바의 객체는 클래스를 기반으로 한다. 클래스 없이는 어떤 프로그램도 만들 수 없다.
    어쨌든 둘 다 객체지향 언어이지만, JS는 객체를 사용하지 않고 간단한 선형 프로그래밍(linear programming)을 허용할 정도로 느슨하다. 두 언어 모두 객체 지향 설계의 주요 요소인 상속과 다형성을 허용한다.

  • JS는 .js라는 파일 확장자를 갖는다. interpreted되지만 compiled되지는 않는다. 모든 브라우저는 JS 코드를 실행할 Javascript interpreter를 갖고 있다.

  • JS는 싱글 스레드 언어이다. 한번에 하나의 작업만 처리한다. 하지만 여러 작업을 동시에 처리하는 것처럼 느껴진다. JS가 이벤트 루프를 기반으로 동시성(concurrency)을 지원하기 때문이다.
    ⇒ 반면, 자바(Java)는 멀티 스레딩을 지원한다. 그러므로 스레드를 기반으로 동시성을 지원한다.

  • JS는 코드를 실행하기 위해 텍스트 에디터 또는 브라우저 콘솔을 필요로 한다.

  • JS는 웹페이지에 포함되어 있으며, HTML 콘텐츠와 통합된다.
    ⇒ 자바는 독립형 언어이다.

  • JS와 자바 모두 함수형 프로그래밍을 지원한다.
    JS의 함수는 1급 시민이다. 함수는 객체처럼 취급되고 다른 함수로 전달될 수 있으며, 필요한 경우 자체 member variables를 전달할 수도 있다.
    Java는 Java 8에서 함수형 프로그래밍을 도입했다.

자바스크립트와 자바 모두 C와 유사한 구문을 가지고 있으며 client-side와 server-side 어플리케이션을 만드는 데 널리 사용되지만 유사점은 거의 없다.

JavaScript라는 이름에 Java가 포함된 건, 일종의 마케팅을 위함이기도 했다.

JavaScript와 Node.js

Node.js는 V8 엔진에 구축된 런타임 환경이다.

js는 브라우저에서 실행되지만, Node.js를 사용하면 브라우저 외부에서 js를 실행할 수 있다.

js는 DOM을 조작하거나 내부에 HTML을 추가할 수 있지만 Node.js에는 HTML을 추가할 수 있는 기능이 없다.

스크립트 언어와 프로그래밍 언어

기본적으로 모든 스크립트 언어는 프로그래밍 언어이다. 다만 스크립트 언어는 컴파일 단계를 필요로 하지 않고 interpreted된다. 예를 들어, C는 프로그램을 실행하기 전에 컴파일해야 하지만, JS는 컴파일할 필요가 없다. 컴파일러는 한번에 모든 코드를 분석하지만, 인터프리터는 코드 문을 만날 때마다 분석하고, 오류가 있으면 그 인스턴스에서 중지한다.

스크립트 언어는 호스트를 필요로하지만, 프로그래밍 언어는 일반적으로 자체 실행이 가능하다.

스크립트 언어와 프로그래밍 언어를 분류할 때 실행될 환경을 고려해볼 수도 있다. C 언어에 대한 인터프리터를 설계해 스크립트 언어로 사용할 수도 있으며 동시에 JS에 대한 컴파일러를 설계해서 컴파일된 언어로 사용할 수도 있다. 예를 들어, google chrome의 JS 엔진인 V8은 JS 코드를 해석하는 대신 machine 코드로 컴파일한다.

V8이 JS 코드를 컴파일하는 방법

V8은 Google Chrome 및 Node.js에서 사용하는 고성능 오픈 소스 JavaScript 및 WebAssembly 엔진이다.

코드 처리는 크게 세 단계로 구분할 수 있다.

  1. 코드 파싱 (Parsing)
  2. 코드 컴파일 (Compiling)
  3. 코드 실행 (Executing)

코드 파싱
파싱 단계에서 코드는 각각의 토큰으로 분해된다.

const sum = 5 + 7

예를 들어 위 코드에서 const, sum, 5, +, 7은 모두 각각 토큰이다. 이렇게 코드가 토큰으로 분해된 후, 코드를 추상 구문 트리(Abstract Syntax Tree (AST))로 변환하는 구문 파서가 주어진다.

코드 컴파일

컴파일은 사람이 읽을 수 있는 코드를 기계 코드로 변환하는 프로세스이다. 코드를 컴파일하는 방법에는 2가지가 있다.

  1. 인터프리터 사용 : 인터프리터는 코드를 1줄씩 스캔해 바이트 코드로 변환한다.
  2. 컴파일러 사용 : 컴파일러는 전체 문서를 스캔해 고도로 최적화된 바이트 코드로 변환한다. (예, Java)

다른 언어와 달리, V8 엔진은 컴파일러와 인터프리터를 모두 사용한다. 그리고 성능 향상을 위해 JIT 컴파일(Just in Time(JIT) Compilation)을 따른다.

V8 엔진은 초기에 인터프리터를 사용해 코드를 해석한 뒤, 추가 실행 시에 자주 실행되는 함수나 자주 사용되는 변수 등의 패턴을 찾아서 컴파일함으로써 성능을 향상시킨다.

코드 실행
바이트 코드는 V8 엔진의 런타임 환경의 메모리 힙과 콜 스택을 사용해 실행된다. 메모리 힙은 모든 변수와 함수에 메모리가 할당되는 장소이다. 콜 스택은 각 개별 함수가 호출될 때, 스택에 push되고 실행한 뒤 pop된다.

인터프리터가 객체 구조를 사용해 코드를 사용할 때,
바이트 코드는 key로 해당 바이트 코드를 처리하는 함수를 value로 처리한다.

V8 엔진은 메모리의 목록 형식으로 값을 정렬하고 map에 저장해 메모리를 절약한다.

let Person = {이름: "GeeksforGeeks"}
사람.나이 = 20;

위 코드를 보면
2번째 줄에서 age 속성으로 새로운 객체를 만들고 다시 person 객체에 연결한다. 이러한 접근 방식은 연결된 리스트를 검색하는 데 linear time이 걸린다는 문제를 갖고 있다. 이 문제를 해결하기 위해 V8 엔진은 Inline Cache(IC)를 제공한다.

Inline Cache(IC)는 객체의 속성 주소를 추적해 조회 시간을 줄이는 데 사용되는 자료구조이다. Feedback Vector를 유지함으로써 함수 내의 모든 LOAD, STORE, CALL 이벤트를 추적한다.

Feedback Vector는 특정 함수의 모든 Inline Cache들을 추적하는 데 사용되는 간단한 배열이다.

예를 들어

const sum = (a, b) => {
    return a+b;
}

위 코드에서 IC는 다음과 같다.
type으로 LOAD를 갖고, value로 UNINIT을 갖는다. UNINIT은 아직 함수가 초기화되지 않았다는 걸 의미한다.

[{ slot: 0, icType: LOAD, value: UNINIT}]

여기서 아래와 같이 함수를 호출하면

sum(5, 10)
sum(5, "GeeksForGeeks")

첫번째 함수 호출에서 IC는 아래와 같이 변경된다. 이 함수는 정수 값(I)에 대해서만 작동하게 된다.

[{ slot: 0, icType: LOAD, value: MONO(I) }]

두번째 함수 호출에서 IC는 아래와 같다.

[{ slot: 0, icType: LOAD, value: POLY[I,S] }]

string이 추가되었으므로 전달된 인수가 정수 또는 문자열 값에 대해서 모두 작동할 수 있게 된다(I, S).

이때 수신된 인수 유형이 수정되지 않으면 함수 실행 시간이 빨라진다. 인라인 캐시는 사용 빈도를 추적하고 Turbofan 컴파일러에 필요한 피드백을 제공한다. 컴파일러는 인터프리터로부터 바이트 코드와 type 피드백(type feedback)을 받아서 코드를 최적화하고 새로운 바이트 코드를 생성한다. 이때 수신된 데이터가 object 유형이면 코드가 실패하면서, 컴파일러는 코드를 디컴파일하고 인터프리터로 돌아가서 피드백을 업데이트한다.

V8 엔진은 사용하지 않는 기능을 지우고, timeouts나 intervals을 clear 하는 방법들로 메모리 힙을 최대한 확보하려 한다.

Garbage Collection

V8 엔진은 내부적으로 Mark and Sweep Algorithm을 사용하는 Orinoco Garbage Collector를 사용해 메모리 힙에서 공간을 확보한다.

Orinoco Garbage Collector은 3가지 방법으로 Garbage를 수집한다.

  1. Parallel
    parallel collection에서, JS의 메인 스레드는 병렬적으로 helper 스레드를 사용해 garbage를 수집한다. 이 동안 main execution이 잠깐씩 멈춘다.
  2. Incremental
    incremental collection에서, JS의 메인 스레드는 incremental 방식으로 차례대로 garbage를 수집한다. 이 방식을 사용하면 메인 스레드의 대기 시간을 추가로 줄일 수 있다.
  3. Concurrent
    concurrent collection에서, JS의 메인 스레드가 방해받지 않고 백그라운드에서 helper 스레드에 의해 전체 garbage가 수집된다.

참고

Difference between Java and JavaScript
What’s the difference between Scripting and Programming Languages?
What is the difference between Java and JavaScript?
What is the difference between JavaScript and Node.js?

How V8 compiles JavaScript code ?

profile
https://medium.com/@wooleejaan

0개의 댓글