JS) 언어별 특성

100·약 20시간 전

JS

목록 보기
2/2
post-thumbnail

JavaScript · Java · Python · C++ 핵심 특성 비교

프로그래밍 언어는 문법만 다른 것이 아니라 타입을 다루는 방식, 객체 모델, 메모리 관리 방식, 함수 호출 방식, 동시성 모델에서 큰 차이가 있다.
JavaScript, Java, Python, C++을 같은 기준으로 비교하면 각 언어가 왜 특정 분야와 프레임워크에서 많이 사용되는지도 함께 이해할 수 있다.

1. 전체 특징 한눈에 보기

구분JavaScriptJavaPythonC++
타입동적 타입정적 타입동적 타입정적 타입
객체 모델Prototype 기반Class 기반Class 기반·동적Class 기반
메모리 관리GCGCReference Counting + GCRAII / 직접 관리
인자 전달Call by ValueCall by ValueObject SharingValue / Reference 선택
실행 환경Browser / Node.jsJVMCPythonNative
실행 방식JIT 중심Bytecode + JITBytecode + VMAOT Compile
동시성 특징Event Loop 중심Multi ThreadGIL 영향Multi Thread
함수일급 객체Lambda / Functional Interface일급 객체Lambda / 함수 포인터
대표 환경React / Node.jsSpringDjango / FastAPISystem / Game / Embedded

2. 실행 방식과 Runtime

언어마다 작성한 코드가 CPU에서 실행되기까지의 과정이 다르다.

C++

Source
  ↓ Compiler
Machine Code
  ↓
CPU
Java

Source
  ↓ javac
Bytecode
  ↓
JVM
  ↓ JIT
Machine Code
Python

Source
  ↓
Bytecode
  ↓
Python VM
JavaScript

Source
  ↓
JS Engine
  ↓
Bytecode / JIT
  ↓
Machine Code

JavaScript

브라우저나 Node.js 같은 Runtime에서 실행된다. V8 같은 JavaScript Engine이 코드를 해석하고 필요에 따라 JIT 컴파일을 이용해 최적화한다.

브라우저에서는 JavaScript Engine뿐 아니라 DOM, Timer, fetch 같은 Web API도 함께 사용한다.

Java

소스 코드를 먼저 Bytecode로 컴파일하고 JVM이 이를 실행한다.

JVM은 실행 중 자주 사용되는 코드를 JIT 컴파일하여 Native Code로 최적화한다. JVM이 메모리 관리, Garbage Collection, Thread 관리도 담당한다.

Python

일반적인 CPython 구현에서는 소스 코드가 Bytecode로 변환되고 Python Virtual Machine이 이를 실행한다.

흔히 인터프리터 언어라고 부르지만 내부적으로 Bytecode 단계가 존재한다.

C++

실행 전에 Native Machine Code로 컴파일된다.

별도의 JVM이나 Python VM 없이 OS와 CPU에서 직접 실행되기 때문에 성능과 제어력이 높다.

핵심

Compile Time
→ 프로그램 실행 전 검사·변환 단계

Runtime
→ 프로그램이 실제 실행되는 시점과 환경

AOT
→ 실행 전에 Machine Code 생성

JIT
→ 실행 중 Machine Code로 컴파일·최적화

3. 타입 시스템

타입 시스템에서 가장 먼저 볼 것은 타입이 언제 결정되고 검사되는가이다.

정적 타입: Java / C++

변수를 선언할 때 타입이 정해지고 컴파일 단계에서 타입을 검사한다.

int age = 20;
String name = "Kim";
int age = 20;
std::string name = "Kim";

잘못된 타입을 사용하면 실행 전에 많은 오류를 발견할 수 있다.

int age = "20"; // Compile Error

동적 타입: JavaScript / Python

변수에 타입을 고정하지 않고 실행 중 값에 따라 타입이 결정된다.

let value = 10;
value = "hello";
value = true;
value = 10
value = "hello"

Python에서는 Type Hint를 사용할 수 있다.

def add(a: int, b: int) -> int:
    return a + b

하지만 기본적으로 Runtime에서 강제되는 것은 아니다.

JavaScript의 특징

암묵적 타입 변환이 존재한다.

1 == "1"   // true
1 === "1"  // false

따라서 일반적으로 === 사용을 권장한다.

핵심

Java / C++
→ Compile Time에 타입 검사

JavaScript / Python
→ Runtime에 타입이 결정되는 동적 타입

4. 값 / 객체 / 참조 / Pointer

객체를 이해할 때 가장 중요한 것은 변수에 실제 값이 들어가는지, 객체를 가리키는 정보가 들어가는지 구분하는 것이다.

JavaScript

Primitive와 Object를 구분한다.

const user = {
    name: "Kim"
};

const a = user;
const b = a;

b.name = "Lee";

console.log(a.name); // Lee

a, b가 같은 객체를 가리키고 있기 때문에 한쪽에서 객체를 수정하면 다른 쪽에서도 보인다.

객체 비교 역시 참조 기준이다.

{} === {} // false

Java

Primitive Type과 Reference Type을 구분한다.

int age = 20;
User user = new User();

개념적으로:

Stack                     Heap

age = 20

user ───────────────────→ User 객체
                           name
                           age

객체 변수에는 객체 전체가 아니라 객체를 가리키는 Reference가 저장된다.

Python

Python에서는 거의 모든 것이 객체다.

a = [1, 2]
b = a

b.append(3)

print(a)  # [1, 2, 3]

변수는 객체 자체라기보다 객체를 가리키는 이름처럼 이해하면 편하다.

C++

값, Pointer, Reference를 명시적으로 구분한다.

int a = 10;

int* p = &a;   // Pointer
int& r = a;    // Reference
  • Pointer → 메모리 주소 저장
  • Reference → 기존 객체의 별칭
  • Value → 값 자체

C++은 이 차이를 개발자가 직접 다룬다는 점이 다른 언어와 크게 다르다.


5. 함수 호출과 인자 전달

Java

Java는 항상 Call by Value다.

void change(User user) {
    user.name = "Lee";
}

객체를 전달할 때도 객체 자체가 아니라 Reference 값의 복사본이 전달된다.

따라서 객체 내부 변경은 반영된다.

void change(User user) {
    user.name = "Lee";
}

하지만 매개변수를 다른 객체로 바꾸는 것은 외부 변수에 영향을 주지 않는다.

void change(User user) {
    user = new User();
}

JavaScript

JavaScript도 Call by Value다.

객체를 전달하면 객체를 가리키는 참조값이 복사된다.

function change(obj) {
    obj.name = "Lee";
}

const user = { name: "Kim" };

change(user);

console.log(user.name); // Lee

Python

Python은 일반적으로 Call by Sharing(Object Sharing)이라고 설명한다.

def change(arr):
    arr.append(3)

a = [1, 2]
change(a)

print(a)  # [1, 2, 3]

Mutable 객체 내부를 수정하면 외부에서도 변경이 보인다.

하지만 재바인딩은 외부에 영향을 주지 않는다.

def change(arr):
    arr = []

a = [1, 2]
change(a)

print(a)  # [1, 2]

C++

C++은 값 전달과 참조 전달을 직접 선택한다.

void change(int x) {
    x = 10;
}

값 전달.

void change(int& x) {
    x = 10;
}

참조 전달.

큰 객체는 복사 비용을 줄이기 위해 const reference를 많이 사용한다.

void print(const User& user) {
}

6. 객체 모델

Java

전형적인 Class 기반 객체지향 언어다.

Class
  ↓
Object
class User {
    String name;

    void hello() {
        System.out.println(name);
    }
}

주요 개념:

Encapsulation
Inheritance
Polymorphism
Abstraction
Interface

C++

C++ 역시 Class 기반 객체지향 언어다.

class Animal {
public:
    virtual void sound() {}
};

class Dog : public Animal {
public:
    void sound() override {}
};

생성자와 소멸자, virtual 함수, 객체 생명주기가 중요한 특징이다.

Python

Python도 Class 기반이지만 훨씬 동적이다.

class User:
    def __init__(self, name):
        self.name = name

user = User("Kim")
user.age = 20

Runtime에 객체 속성을 추가하는 것도 가능하다.

함수와 클래스 자체도 객체이기 때문에 Decorator 같은 기능도 자연스럽게 사용할 수 있다.

JavaScript

JavaScript는 본질적으로 Prototype 기반이다.

Object
  ↓
Prototype
  ↓
Prototype

예를 들어:

const arr = [1, 2, 3];

arr.map(x => x * 2);

map()을 찾을 때 Prototype Chain을 따라 Array.prototype까지 탐색한다.

JavaScript의 class는 Prototype 기반 객체 모델을 편리하게 사용하는 문법이다.


7. Mutable / Immutable / Equality

Mutable / Immutable

Mutable은 객체 내부 상태를 변경할 수 있다는 의미이고, Immutable은 생성된 값을 변경할 수 없다는 의미다.

JavaScript

객체와 배열은 기본적으로 Mutable이다.

const user = {
    name: "Kim"
};

user.name = "Lee"; // 가능

const는 객체가 Immutable이라는 의미가 아니다.

user = {}; // Error

단지 변수 재할당을 막는다.

Java

일반 객체는 Mutable할 수 있지만 String은 Immutable이다.

String s = "hello";

s = s + " world";

기존 문자열이 바뀐 것이 아니라 새로운 String 객체가 만들어진다.

Python

Mutable:

list
dict
set

Immutable:

int
float
str
tuple

C++

const를 통해 변경 가능성을 적극적으로 제어한다.

const int x = 10;

void print(const User& user) {
}

Equality / Identity

값이 같은 것과 같은 객체인 것은 다른 개념이다.

JavaScript

{} === {} // false

객체는 Reference를 비교한다.

Java

a == b       // Reference 비교
a.equals(b) // 값 비교

Python

a == b // 값 비교
a is b // 동일 객체 비교

C++

operator==를 오버로딩하여 값 비교 의미를 직접 정의할 수 있다.

bool operator==(const User& other) const {
    ...
}

8. 메모리 관리와 객체 생명주기

JavaScript

Garbage Collector가 사용하지 않는 객체를 정리한다.

객체 생성
↓
Reference 존재
↓
Reference가 모두 사라짐
↓
GC 대상

하지만 Closure, Event Listener, Timer 등이 객체를 계속 참조하고 있으면 불필요한 객체가 유지될 수 있다.

Java

JVM의 Garbage Collector가 Heap 객체를 관리한다.

GC Root에서 더 이상 도달할 수 없는 객체가 수거 대상이 된다.

GC가 있다고 해서 메모리 누수가 불가능한 것은 아니다.

불필요한 Reference 유지
→ 객체가 계속 Reachable
→ GC가 제거하지 못함

Python

주로 Reference Counting 방식으로 관리한다.

Object Reference Count
3 → 2 → 1 → 0
             ↓
           제거

순환 참조는 별도의 Garbage Collector가 처리한다.

C++

기본적인 Garbage Collector가 없다.

대신 RAII(Resource Acquisition Is Initialization)가 핵심이다.

{
    std::lock_guard<std::mutex> lock(mutex);

    // 작업
}

Scope가 끝나면서 객체가 소멸되고 Lock도 자동 해제된다.

Smart Pointer도 사용한다.

std::unique_ptr<User>
std::shared_ptr<User>
std::weak_ptr<User>

9. 함수 / Scope / Closure

JavaScript

함수가 일급 객체다.

const add = (a, b) => a + b;

function run(fn) {
    return fn(1, 2);
}

함수를 변수에 저장하고, 인자로 전달하고, 반환할 수 있다.

Closure도 핵심이다.

function outer() {
    let count = 0;

    return () => ++count;
}

const counter = outer();

counter(); // 1
counter(); // 2

JavaScript는 Lexical Scope를 사용한다.

또한 일반 함수와 화살표 함수의 this 동작이 다르다.

Python

Python에서도 함수는 일급 객체다.

def add(a, b):
    return a + b

fn = add
fn(1, 2)

Decorator와 Closure도 자주 사용한다.

def decorator(fn):
    def wrapper():
        print("before")
        fn()

    return wrapper

Python의 Scope는 LEGB 규칙을 따른다.

Local
Enclosing
Global
Built-in

Java

기본적으로 객체와 Method 중심이다.

Lambda와 Functional Interface를 통해 함수형 스타일을 사용할 수 있다.

list.stream()
    .filter(x -> x > 10)
    .map(x -> x * 2)
    .toList();

C++

Lambda와 함수 포인터를 사용할 수 있다.

auto add = [](int a, int b) {
    return a + b;
};

외부 변수 Capture도 가능하다.

[=] // 값 Capture

[&] // Reference Capture

10. Thread / 동시성 / 비동기

먼저 세 개념을 구분해야 한다.

Concurrency
→ 여러 작업을 함께 진행시키는 구조

Parallelism
→ 실제 여러 작업을 동시에 실행

Asynchronous
→ 작업 완료를 기다리지 않고 다른 작업 수행

JavaScript

기본 JavaScript 실행은 하나의 Main Thread에서 이루어진다.

Call Stack
    ↓
Event Loop
    ↓
Microtask Queue / Task Queue

Timer, Network, DOM Event 등은 Browser 또는 Node.js Runtime이 처리한다.

console.log("A");

setTimeout(() => {
    console.log("B");
}, 0);

console.log("C");

결과:

A
C
B

Promise와 async/await도 Event Loop와 연결된다.

async function getUsers() {
    const response = await fetch("/api/users");
    return response.json();
}
Single Thread ≠ 동시성 불가능
Async ≠ Multi Thread

Java

멀티스레드를 적극적으로 활용한다.

Request A → Thread 1
Request B → Thread 2
Request C → Thread 3

여러 Thread가 같은 객체를 공유하기 때문에 Thread Safety가 중요하다.

대표적으로:

synchronized
volatile
Atomic
ExecutorService
CompletableFuture
Virtual Thread

등을 사용한다.

Python

CPython에는 GIL(Global Interpreter Lock)이 있다.

여러 OS Thread를 만들 수 있지만 한 시점에는 하나의 Thread만 Python Bytecode를 실행한다.

따라서 일반적으로:

CPU Bound
→ multiprocessing

I/O Bound
→ threading / asyncio

방식을 사용한다.

asyncio는 Coroutine과 Event Loop를 이용한다.

C++

OS Thread를 직접 사용할 수 있다.

std::thread
std::mutex
std::atomic
std::condition_variable

그만큼 다음 문제를 직접 고려해야 한다.

Race Condition
Data Race
Deadlock

11. 프레임워크와 언어 특성

언어의 특성은 실제 프레임워크 설계에도 그대로 드러난다.

JavaScript + React / Node.js

React에서 특히 중요한 개념:

Function
Closure
Reference Equality
Immutability
Event Loop
Async

상태 변경과 렌더링을 제대로 이해하려면 객체 참조와 Closure를 이해해야 한다.

Node.js에서는 Event Loop와 비동기 I/O가 핵심이다.


Java + Spring

Spring에서 중요한 Java 특성:

Class / Object
Reflection
Annotation
Proxy
GC
Multi Thread
Thread Safety

Spring Bean은 기본적으로 Singleton이므로 여러 Thread가 동일 Bean을 동시에 사용할 수 있다.

따라서 Mutable 상태를 Bean 필드에 함부로 저장하면 Thread Safety 문제가 발생할 수 있다.


Python + FastAPI / Django

FastAPI:

Type Hint
Decorator
async / await
Coroutine

Python의 동적인 특성과 비동기 기능을 적극적으로 활용한다.

Django 역시 Class, Decorator, ORM 등 Python의 객체 모델을 많이 활용한다.


C++

C++은 특정 하나의 웹 프레임워크보다 다음 분야에서 많이 사용된다.

System Programming
Game Engine
Embedded
High Performance Application

이 환경에서는 특히:

Pointer / Reference
RAII
Object Lifetime
Memory Safety
Thread

에 대한 이해가 중요하다.


12. 핵심 비교

JavaScript
→ 동적 타입
→ Prototype 기반 객체 모델
→ Garbage Collector
→ 함수 / Closure 중심
→ Event Loop 기반 동시성

Java
→ 정적 타입
→ Class 기반 객체지향
→ JVM + Garbage Collector
→ Reference를 Call by Value로 전달
→ Multi Thread와 Thread Safety 중요

Python
→ 동적 타입
→ 거의 모든 것이 객체
→ Reference Counting + GC
→ 함수가 일급 객체
→ GIL + asyncio / multiprocessing

C++
→ 정적 타입
→ Class + Pointer + Reference
→ Native 실행
→ RAII 기반 자원 관리
→ Memory / Thread를 개발자가 직접 제어

결국 네 언어를 비교할 때는 다음 질문을 중심으로 보면 된다.

1. 코드는 어떤 Runtime에서 어떻게 실행되는가?
2. 타입은 언제 결정되고 검사되는가?
3. 변수는 값과 객체를 어떻게 저장하는가?
4. 함수에 인자를 넘기면 무엇이 전달되는가?
5. 객체 모델은 Class인가 Prototype인가?
6. 객체는 Mutable인가 Immutable인가?
7. 메모리와 객체 수명은 누가 관리하는가?
8. 함수와 Scope는 어떤 특징을 가지는가?
9. 여러 작업을 Thread / Event Loop / Async 중 어떻게 처리하는가?
10. 이러한 특성이 프레임워크에서 어떻게 활용되는가?
profile
멋있는 사람이 되는 게 꿈입니다

0개의 댓글