Language/Javascript

[javascript] Java와 Js의 객체의 차이 // Js의 클래스, 프로토타입, prototype 기반 언어인 이유

기록하는 습관. 2025. 1. 4. 00:10

Javascript를 학습하다보면 객체를 빼놓을 수 없습니다.


저는 주로 Java를 주로 사용합니다. Java에서도 객체는 빠질 수 없는 개념이며 객체 기반으로 동작하게 됩니다.
이는 객체 지향 프로그래밍을 가능하게 합니다. 즉, 만들어진 객체를 서로 상호작용하게 하여 프로그래밍을 동작할 수 있도록 해줍니다.


Javascript의 메커니즘도 같습니다. 객체를 만들어 여러 곳에서 사용할 수 있게 합니다. 하지만 Javascript는 proto type 기반 언어라고 이야기하는데 객체와 더불어 클래스를 이해하고 Java와 Javascript의 차이에 대해 정리하겠습니다. (호이스팅에 대해서는 다루지 않습니다.)


STEP 1. 객체

STEP 1-1. Java의 객체

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void introduce() {
        System.out.println("Hi, my name is " + name + " and I am " + age + " years old.");
    }

    public static void main(String[] args) {
        System.out.println("=======================");
        System.out.println("=======================");
        System.out.println("=======================");
        System.out.println("=======================");
        Person person = new Person("Alice", 25);
        person.introduce(); // 출력: Hi, my name is Alice and I am 25 years old.
    }
}

 Java는 class를 통해 설계도를 만들고 new 키워드를 통해 인스턴스(객체)를 생성합니다.


 메모리 관점으로 Java의 인스턴스 생성까지 조금 더 알아보겠습니다.
프로그램이 실행되면 Java에 정의된 class는 JVM의 메소드 영역(Method area 또는 Static area)에 저장되게 됩니다. 메소드 영역은 모든 스레드가 공유합니다. Java는 순차적인 실행하는 동안 new 키워드를 읽게 되면 메소드 영역에 저장된 클래스를 참조하여 인스턴스를 만들고 만들어진 인스턴스는 힙(Heap)영역에 저장됩니다. 이때, 만들어진 인스턴스는 개별 객체별로 데이터를 저장하기 떄문에 독립된 데이터를 가집니다


 Java와 Javascript의 차이의 핵심은 객체의 생성을 어떻게 하는가?에 있습니다.
내용이 잘 이해가 되지 않는다면 Java는 new 키워드를 만났을 때 객체(인스턴스)를 만든다. 그리고 Java에서의 메소드는 독립적으로 존재할 수 없다. 즉, class 내부에 정의 되어 항상 클래스나 객체와 함께 존재한다. 라고 이해하며 다음 글을 읽으면 도움이 될 것입니다.

STEP 1-2. Javascript의 객체

STEP 1-2-1. Javascript 함수와 객체

// 함수 선언식 사용
function addNumbers(a, b) {
    return a + b;
}

// 함수 호출
const result = addNumbers(3, 5);
console.log(result); // 출력: 8

 Javascript는 Java와 달리 독립적으로 함수가 존재할 수 있습니다.
즉, class 외부에 함수가 존재할 수 있음을 시사합니다. 위와 같은 코드를 실행했다고 가정하겠습니다.
해당 프로그램을 실행한다면 const result = addNumbers(3, 5);함수를 호출하기 이전부터 함수 선언식 addNumbers(a, b)는 Javascript의 메모리에 객체로 존재하게 됩니다.

만들어진 객체는 Javascript의 힙(Heap)영역에 저장되게 됩니다.


STEP 1-2-2. Javascript 함수 안에 함수와 객체 그리고 스코프

function outerFunction() {
    function innerFunction() {
        console.log("This is the inner function.");
    }
}

innerFunction(); // ReferenceError: innerFunction is not defined

 함수안의 함수 동작에 대해서 보겠습니다. Javascript의 함수는 객체로서 존재한다. 라는 것을 이해했습니다.
이를 이유로 outerFunction()과 innerFunction()는 호출이전부터 객체로 존재할 것입니다. 다름이 없습니다.


그러면 innerFunction();을 호출하면 어떻게 될까요? ReferenceError: innerFunction is not defined 를 출력하게 됩니다. 문제는 스코프(Scope)에 있습니다. 함수 내부에 정의된 변수나 함수는 외부에서 직접 접근할 수 없습니다. 만약 innerFunction()을 동작하게 하고 싶으면 아래와 같은 코드를 사용하면 됩니다.


function outerFunction() {
    function innerFunction() {
        console.log("This is the inner function.");
    }

    return innerFunction; // innerFunction을 반환
}

const inner = outerFunction();
inner(); // This is the inner function.

 outerFunction()에서 innerFunction을 return하여 함수자체를 반환하게 합니다.
이후, inner라는 변수를 정의하고 outerFunction();을 호출하여 함수 자체를 반환받습니다.
그리고 inner()로 함수를 호출하면 됩니다.

 "return innerFunction()이 아닌 이유"

함수자체의 반환과 함수의 실행에 대한 차이가 있음을 알아야합니다.
만약 outerFunction()의 반환값이 return innerFunction()라면 innerFunction()의 반환값을 받는다는 의미가 됩니다. 따라서 정의한 innerFunction()도 반환하려는 retrun 문을 포함하여 반환해주는 것이 바람직합니다.


⭐STEP 2. Javascript의 클래스

class User {
  constructor(name) { 
      this.name = name; 
  }
  sayHi() { 
      console.log(this.name); 
  }
}

console.log(typeof User); // function

 JavaScript의 클래스는 함수의 한 종류입니다. console.log(typeof User); 코드는 이를 증명합니다.
클래스 선언 자체는 메모리에 등록되지만, 클래스의 인스턴스(객체)는 new 키워드를 사용해야만 생성됩니다.
따라서, 클래스 선언만으로는 객체가 메모리에 생성되지 않으며, new 키워드를 사용해야 객체를 초기화할 수 있습니다.


그러면 클래스가 정의되는 시점의 sayHi()도 메모리에 올라가지 않는 것일까요?
결론부터 말씀드리면 sayHi()는 메모리에 올라가게 됩니다. 이유는 클래스가 내부에 정의된 메서드들은 클래스가 정의하는 시점에 이미 프로토타입에 등록하기 때문입니다. 프로토타입에 등록한다는 것은 클래스가 만드는 각 인스턴스마다 메서드 객체를 생성하지 않고 프로토타입 객체를 만들어 모든 인스턴스가 해당 메서드 객체를 공유한다는 것을 의미하고 메모리를 효율적으로 사용할 수 있게 합니다.


image

// 내부구조

User: {
  prototype: {                     // 프로토타입 객체
    constructor: User,             // 프로토타입 객체의 기본 프로퍼티 (constructor 프로퍼티)
    sayHi: [Function: sayHi]       // prototype 메서드 (sayHi 프로퍼티)
  }
}

 그림으로 User클래스를 이해해보겠습니다.
클래스가 정의(선언)된 시점에서 User클래스는 본문(body)로 constructor 메서드를 가집니다.
또한 User.propertype객체가 만들어지는데 이는 프로토타입 객체이며 기본 프러퍼티인 constructorsayHi프로퍼티를 가집니다. constructor 프로퍼티는 User 클래스를 참조합니다.


const user1 = new User("Alice");
const user2 = new User("Bob");
// 메모리 구조
User
  └── prototype (프로토타입 객체)
      ├── constructor: User
      ├── sayHi: function

user1 (인스턴스)
  ├── name: "Alice"              // 개별 인스턴스 프로퍼티
  └── __proto__: User.prototype   // 프로토타입 연결

user2 (인스턴스)
  ├── name: "Bob"                // 개별 인스턴스 프로퍼티
  └── __proto__: User.prototype   // 프로토타입 연결

 선언한 이후에, 코드 실행단계에서 클래스의 인스턴스 user1user2를 생성한다면 메모리 구조는 위와 같습니다. 각 인스턴스는 고유한 메모리 주소를 가집니다. 눈여겨 보아야 할 것은 생성된 인스턴스에 __proto__프러퍼티가 공통적으로 있는 것입니다. 이는 클래스의 프로토타입 객체의 주소 값을 가지며 프로토타입과 연결합니다.


이렇게하여 하나의 프로토타입 객체를 모든 인스턴스가 사용할 수 있게됩니다.


image

console.log(Object.getOwnPropertyDescriptors(User.prototype), "\n"); 
console.log(User.prototype.sayHi, "\n");//  [Function: sayHi]
console.log(User.prototype.sayHi()); // Hello!

 User.prototype은 클래스 User의 프로토타입 객체이며, 프로토타입 객체는 constructor기본 프로퍼티와 sayHi 프로러티를 가집니다.

 해당 코드를 통해 말하고 정리하고자 하는 것은 클래스를 new 키워드로 초기화하지 않았음에도 프로토타입 객체객체이기 때문에 메모리에 상주하게 된다는 것입니다. 더불어 프로토타입 객체는 각기 다른 인스턴스가 같이 사용하는 공용 객체입니다.




⭐정리 : Java와 Javascript의 차이

  두 언어는 많은 차이점을 갖고 있지만 각각의 언어가 본질적으로 지향하는 방식에 대해서 이야기하겠습니다.


 "Java는 객체지향 언어이고, Javascript는 객체지향도 지원하지만 본질적으로는 프로토타입 기반 언어다"
현재까지 작성한 내용으로는 말하고자 하는 부분이 빈약할 수 있습니다. Java의 경우 상속을 하기 위해선 class 상속을 이용합니다. 반면에 Javascript는 프로토타입 기반 상속을 합니다.

const person = {
  name: "Alice",
  sayHi: function () {
    console.log(`Hi, I'm ${this.name}`);
  },
};

// 새로운 객체(developer)를 생성하며, 그 객체의 프로토타입을 person으로 설정
const developer = Object.create(person);

developer.sayHi(); // Hi, I'm undefined
developer.name = "Bob";
developer.sayHi(); // Hi, I'm Bob
developer.language = "JavaScript";
// 실행된 후의 메모리 구조

Object.prototype
  ├── toString: function
  ├── hasOwnProperty: function
  └── 기타 기본 메서드들

person
  ├── name: "Alice"             // person의 own property
  ├── sayHi: function           // person의 own property
  └── __proto__: Object.prototype // 기본 프로토타입 객체

developer
  ├── name: "Bob"               // developer의 own property
  ├── language: "JavaScript"    // developer의 own property
  └── __proto__: person        // person 객체를 참조

 기본개념

 person은 생성자 함수로 만들어진 경우가 아니기 때문에 person객체는 기본적으로 Object.prototype을 가집니다. 따라서 person객체의 숨겨진 프로퍼티 __proto__Object.prototype을 참조합니다.


  코드의 설명

 const developer = Object.create(person); 코드로 새로운 객체를 생성하며, 그 객체의 프로토타입을 person으로 설정합니다. developer 객체를 생성 직후 시점에는 name을 가지고 있지 않습니다. 따라서 developer.name = "Bob"; 코드로 developer객체가 name 프로퍼티를 가질 수 있게 작성해줍니다.
만약, 다른 객체와 달리 language 프로퍼티를 갖게 하고 싶은 경우라면 developer.language = "JavaScript";를 작성해줍니다.


  정리

 developer객체가 person 객체를 참조한다는 것은 person__proto__를 참조하는 효과를 발휘합니다.
따라서 Java 프로그래밍과 같이 부모 클래스를 참조하는 객체 지향 프로그래밍이 가능하며, Javascript의 본질은 클래스가 아닌 프로토타입 객체 기반의 참조가 이루어지므로 prototype 기반 언어라고 할 수 있습니다.

'Language > Javascript' 카테고리의 다른 글

[Javascript] form태그의 onSubmit 속성 알아보기  (0) 2024.06.02