C 사용자의 JavaScript 사용 후기

Heojoooon·2023년 2월 3일
0

웹 개발을 접하기 전까지 대학, 취준, 회사에서 가장 많이 사용했던 언어가 C이다.
그다음으로는 C++ 정도?
그래서 처음 JavaScript를 접했을 때의 느낌을 글로 적어보려 한다.

잘못되었거나 수정이 필요한 내용이 있을 수 있습니다.
그런 경우, 댓글로 남겨주시면 정말 감사하겠습니다.

int가 없어?

처음 JavaScript로 코드를 작성할 때, 나도 모르게 입으로 나온 말이다.
C를 쓰던 나에게는 큰 충격이였다.
C와 같은 정적 타입 언어코드가 기계어로 컴파일될 때 타입이 정해진다.
따라서 변수 선언 시에 타입을 같이 명시해주어야 한다.
따라서 컴파일 과정에서 타입을 비교하기 때문에 관련 오류를 미리 찾아낼 수 있다는 장점이 있다.

하지만 JavaScript는 var, let, const라는 키워드를 이용하여 변수를 선언하는 정적 타입 언어이다.
변수에 구체적인 타입을 명시하는 것이 아닌, 위의 키워드를 사용하여 변수를 선언하고, 런타임에 타입이 결정된다.
따라서 타입에 대해 굉장히 너그럽다.

const a = 10;

위와 같이 코드를 작성하면 런타임에 '아 a는 number type이구나!' 하고 알아서 타입을 결정해준다.

처음 사용했을 때에는 굉장히 편하다라는 생각을 가지고 있었는데, 점점 코드가 길어지고 복잡해지는 경우에는 TypeError를 뿜어내고 어디에서 오류인지를 찾느라 골치가 아팠다.

결정적으로 함수가 어떤 타입을 리턴하는지 몰라 기능을 유추하기가 힘들었다.

회사에서 C로 개발을 할 때에는 함수 이름, 그리고 리턴하는 구조체를 보고 어느정도 함수 기능에 대한 유추를 하고 코드를 보았고 이럴 경우 코드를 해석하는데 굉장히 도움이 많이 되었다.
하지만 JavaScript로 작성한 코드를 볼 때에는 오직 이름만으로 유추를 해야했기 때문에 해석에 시간이 더 많이 들었다.

TypeScript

위와 같은 문제를 보완하기 위해 나온 것이 TypeScript라고 생각한다.
TypeScript는 다음과 같은 과정을 통해 동작한다.

TypeScript 소스코드를 AST로 Parsing
Type checker가 만들어진 AST의 타입들을 검사한 후, JavaScript 소스코드로 변환

따라서 JavaScript 코드로 변환하는 과정에서 타입을 비교하기 떄문에 미리 타입과 관련된 오류를 잡을 수 있다.
또한 C에서 structure를 정의하여 사용하듯 typeinterface를 사용하여 타입을 custom하고, 직접적으로 타입을 명시해주기 때문에 훨씬 안정감 있고, 유추 또한 수월해졌다.

Single Thread?

JavaScript의 특징이라 하면 가장 먼저 나오는 말이 Single Thread이다.
C의 경우, 작성한 코드를 실행시키면 독립적인 프로그램이 실행된다.

즉, 독립적인 프로세스로 다른 프로세스들과 CPU를 공유하며 멀티 태스킹 환경에서 실행된다.

하지만 JavaScript의 경우, 웹 브라우저 프로세스 내에서 실행되고 있는 thread 중 하나의 thread에서 실행된다.

즉, JavaScript 코드는 JavaScript Runtime 내의 V8 Engine에서 실행된다.

V8 Engine은 하나의 Call Stack과 Heap을 갖는다.
그리고 코드(정확히는 실행 컨텍스트)를 실행시키는 Call Stack이 하나이기 때문에 Single Thread의 특징을 갖는다.

Single Thread의 의미는 한번에 하나의 일만을 처리할 수 있다는 말이다.

그렇다면 엄청나게 큰 일을 하게 될 경우, 모든 일을 중단시킬 수 있지 않을까?

라는 생각을 하지 않을 수 없다.

비동기?

대학생활 동안 가장 이해가 잘 안되었던 말이 Asynchronous 인데, 특정 수업 외에는 언급이 없었기 때문에 졸업 이후 들은 적이 거의 없다.
하지만 비동기라는 말을 웹 개발을 접하면서 거의 밥먹듯이 듣다시피 했다.

동시에 일어나지 않는다.

비동기에 대해 검색하면 가장 많이 나오는 말이다.
처음에는 이게 뭘까에 대해 많은 생각을 했고 정말 이해가 되지 않았다.

그렇다면 반대로 동기의 개념은 "동시에 일어난다."이다.
즉, 코드가 실행되면 코드가 끝이 날 때, 결과가 즉시 발생한다는 것이다.

function sumAll(list) {
	return list.reduce((a, b) => a + b, 0);
}

const total = sumAll(numberList);

위 코드의 경우, sumAll 함수가 실행된 후 return 값이 total에 즉시 저장이 된다.
이러한 경우, 동기적으로 동작한다고 말할 수 있다.

하지만 다음의 상황을 생각해보자.

  1. 만약, 특정 서버에 요청을 보내고 데이터를 받아야 한다면, 클라이언트 입장에서는 요청에 대한 응답이 즉시 온다는 보장 없이 계속 기다리고 있을 수는 없다.
  2. 그리고 웹 페이지에서 하루마다 갱신해야 하는 데이터가 있는 경우, 24시간에 대한 타이머를 작동하기 위해 모든 작업이 block되는 것은 있을 수 없는 일이다.

따라서 JavaScript에서는 이것을 비동기 개념을 이용하여 처리한다.
나는 다음과 같이 개념을 정의하고 비동기에 대해 이해하였다.

"실행과 그에 대한 결과가" 동시에 일어나지 않는다.

위의 그림은 JavaScript Runtime을 검색하면 가장 많이 나오는 그림이다.
왼쪽의 큰 사각형이 위에서 설명한 V8 엔진이다.

JavaScript Runtime은 비동기 작업에 대해 다음과 같이 동작한다고 이해하였다.

  1. Call Stack에 실행될 코드가 들어온다.
  2. 이때 비동기 작업 처리가 필요하다면 관련 Web API를 호출하고 Call Stack을 빠져나간다.
    (setTimeout의 경우 timer Web API)
  3. Call Stack은 다음 코드를 실행시켜 나가고, 호출한 Web API가 완료된다면 비동기 함수를 Callback Queue에 넣는다.
  4. Event Loop는 Call Stack을 모니터링하고 있다가, 비어 있는 상태를 발견하면 Callback Queue에 들어있는 작업들은 Call Stack에 넣는다.

그렇다면 1번 상황의 경우, 다음과 같이 동작한다. (이 부분이 조금 확실하진 않아요.)

  1. Call Stack에 서버에 요청을 하는 코드가 들어가 실행된 후, Call Stack을 빠져나간다.
  2. 그림의 Web API를 통해 서버에 대한 요청이 실행된다.
  3. 요청이 완료된 후, 데이터가 들어있는 Promise가 Callback Queue에 전달된다.
  4. Event Loop가 Call Stack이 비어있는지 확인하고 비어있다면 Callback Queue에 들어가있는 처리할 작업들을 Call Stack에 던져주고, 받아온 데이터의 확인이 가능해진다.

이러한 방식으로 JavaScript는 Single Thread로 돌아가지만 blocking되지 않고, 작업을 처리할 수 있다.
(여기서 등장한 Event Loop에 대한 개념은 다른 글로 다시 한번 정리하려고 한다.)

profile
별 3개짜리 개발자

0개의 댓글