📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄

[JavaScript] 객체 지향 프로그래밍을 위한 자바스크립트 클래스 문법

객체 지향 프로그래밍과 클래스 문법

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHi() {
        console.log(`Hello! My Name is ${this.name}`);
    }
}

const kim = new Person('kim', 30); // {name: 'kim', age: 30}
kim.sayHi(); // Hello! My Name is kim

자바스크립트의 클래스 문법은 ES6에서 도입되어 객체 지향 프로그래밍을 더 쉽게 구현하도록 도와주는 역할을 한다. 여기서 객체 지향 프로그래밍이란 객체라는 독립적인 단위로 나누고, 이 객체들을 재활용하여 관리하는 패러다임을 말한다. 쉽게 이해하자면, 하나의 실리콘 틀(=객체)에 주스를 넣어 아이스크림을 만들거나 젤라틴을 넣어 젤리를 만들듯이 프로그래밍에서도 하나의 객체를 만들어서 여러 방면으로 사용할 수 있게 하는 것을 말한다.

 

즉, 자바스크립트의 클래스 문법은 우리가 객체 지향적 프로그래밍을 하기 위해 존재하며, 이것이 우리가 클래스 문법을 사용하는 본질적인 이유이기도 하다.

 

클래스 문법은 객체의 프로퍼티 부분인 constructor 부분과 메서드 부분으로 구분된다. constructor에서는 this를 이용해서 인스턴스의 프로퍼티(속성)를 정의한다. 위 코드에서 kim 변수는 new Person으로 인스턴스가 생성된 걸 볼 수 있는데, 변수 kim을 콘솔에 찍어보면 객체가 리턴되는 것을 볼 수 있다. (객체를 소프트웨어에 실체화하면 그것을 인스턴스라고 부른다.)

 

🤔 인스턴스란?
1) 프로그래밍에서 클래스의 정의에 따라 생성된 구체적인 객체를 의미한다. 클래스는 객체의 구조와 행동을 정의하는 청사진이며, 인스턴스는 이 청사진을 기반으로 실제로 생성된 객체이다.
2) 클래스의 설계를 구체화한 객체를 뜻한다. 클래스는 공통된 구조와 행동을 정의하고, 인스턴스는 이 구조와 행동을 실제 데이터로 채워서 구체적인 객체를 만든다. 이를 통해 클래스는 여러 개의 인스턴스를 생성할 수 있으며, 각 인스턴스는 독립적인 속성을 가지면서도 공통된 메서드를 공유한다.

 

생성자 함수와 클래스 문법은 뭐가 다를까?

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.greet = function() {
    console.log('Hello!');
}

const kim = new Person('Kim', 30);
kim.greet();

자바스크립트의 클래스 문법이 나오기 전 우리는 인스턴스를 생성자 함수를 이용해서 구현했었다. 생성자 함수의 구현 방식은 클래스 문법과 비슷하면서도 조금 덜 직관적이다. 생성자 함수는 함수를 이용해서 인스턴스 객체를 만들고, prototype을 통해 메서드를 구현한다. 

 

클래스와 생성자 함수의 차이점을 보자면, 먼저 문법적으로 클래스 문법이 더 직관적이며 객체 지향 언어의 클래스를 사용하는 방식과 유사하다. 또한, 클래스 문법에서는 메서드를 클래스 내에 직접 정의하기 때문에 생성자 함수보단 아무래도 더 간결하고 명확하기 때문에 코드 작성과 읽기가 더 쉽다. (ES6의 흐름에 맞춰 클래스를 사용하자!)

 

부모 클래스와 자식 클래스의 상속 관계

class Color {
    constructor(color) {
        this.color = color;
    }
    
    fill() {
        console.log('fill the background!');
    }
}

class Rainbow extends Color {
    constructor(color) {
        super(color); // 부모 클래스인 Color로 color 인수를 전달
    }

    draw() {
        super.fill(); // Color 클래스의 fill 메서드 호출
        console.log('drawing~');
    }
}

const color = new Rainbow('red'); // {color: 'red'}
color.draw();

객체 지향은 곧 재활용이고, 재활용이면 곧 상속도 가능하다는 의미이다. 클래스 문법에도 역시 상속이라는 개념이 존재한다. 자식 클래스는 부모 클래스에게 인스턴스와 메서드를 상속받을 수 있으며, 자식 클래스에서는 부모 클래스의 메서드를 오버라이딩(덮어쓰기) 할 수도 있다. 

 

위 코드를 보면 부모 클래스인 Color 클래스와 자식 클래스인 Rainbow 클래스가 있다. 자식 클래스에서는 extends를 통해 부모 클래스를 정할 수 있으며, super를 통해 부모 클래스로 인수를 전달하거나 부모 클래스의 메서드를 호출할 수 있다.

(자식 클래스에서 super(color)로 color 인수를 부모 클래스의 constructor로 전달하는 것을 확인할 수 있다.)

 

정적 메서드와 인스턴스 메서드

class MathUtilities {
    static add(a + b) {
        return a + b;
    }
    
    multiply(a, b) {
        return a + b;
    }
}

console.log(MathUtilities.add(5, 10)); // 15 → 정적 메서드

const calculate = new MathUtilities();
calculate.multiply(5, 10); // 50 → 인스턴스 메서드

클래스의 메서드에는 정적 메서드와 인스턴스 메서드가 있다. 일반적으로 우리가 클래스를 사용할 때 구현되는 메서드는 인스턴스 메서드이다. 인스턴스 메서드new 연산자를 통해 먼저 인스턴스를 생성하고, 그다음에 생성된 인스턴스에 접근해서 메서드를 호출한 방식을 말한다. 대부분 이 방식으로 메서드를 호출할 텐데, 정적 메서드는 이것과 조금 다르다.

 

정적 메서드는 예시 코드에서 볼 수 있듯이 인스턴스가 필요 없다. 위 코드의 add 메서드는 인스턴스에 접근해서 호출되고 있지 않고, 바로 클래스에 접근해서 메서드를 호출하고 있다. 'static' 키워드로 정의된 정적 메서드는 클래스의 인스턴스가 아닌 클래스 자체에 속하기 때문에 클래스의 인스턴스 없이도 호출될 수 있는 것이다.

(일반적으로 특정 객체와 관련이 없는 유틸리티 함수나 클래스 레벨의 동작을 정의하는 데 사용된다.)

 

🤔 그냥 간단하게 클래스말고 객체(리터럴)로 구현하면 안되나요?
물론, 코드가 간단하고 직관적일 때는 클래스말고 객체로 간단하게 구현하는 것이 적합하다. 하지만, 프로젝트가 커지고 여러 객체 간 상속 관계나 재사용성을 고려했을 때는 클래스를 사용하는 것이 더 좋다. (클래스가 코드 구조를 명확하게 하고, 유지보수와 확장에 더 용이하다.)
1. 클래스 문법을 사용하면 객체 지향 프로그래밍 스타일을 더 명확하고 직관적이게 표현 가능
2. constructor를 통해 인스턴스 속성을 설정한다. (this 키워드를 사용)
3. new 키워드를 사용해 인스턴스를 생성한다.
3-1. 인스턴스란? 클래스의 정의에 따라 생성된 객체를 의미한다.
4. 클래스끼리는 상속이 가능한데, super 키워드를 통해 부모 클래스에 접근할 수 있다.
5. 인스턴스 메서드는 클래스의 인스턴스에 속하는 메서드로, 인스턴스 객체를 통해 호출된다.
6. 정적 메서드는 클래스 자체에 속하는 메서드로, 클래스 이름을 통해 직접 호출된다.