Front/JavaScript

클래스(class) - javascript 기본

oodada 2023. 10. 5. 14:35

클래스 (class)

Class는 객체를 만들기 위한 템플릿이며, 프로토타입 기반 상속을 보다 명시적이고 간편하게 사용할 수 있도록 해줍니다.

  • class를 사용하지 않으면, 객체를 생성하고 속성과 메서드를 추가하는 과정이 번거롭고, 중복된 코드가 많아질 수 있습니다.
  • class를 통해 원하는 구조의 객체 틀을 만들고, 이를 통해 객체를 생성하고 속성과 메서드를 추가할 수 있습니다.
  • 클래스 = 붕어빵 틀 / 객체 = 붕어빵

0. 클래스의 개념

"커피숍"을 운영한다고 가정하고, 커피숍에서 판매하는 다양한 종류의 커피를 클래스를 사용하여 모델링해 보겠습니다.

- 클래스를 사용하지 않은 경우

// 생성자 함수를 사용하여 커피 객체를 생성
function OrderCoffee(name, price, size) {
    // 커피의 속성
    this.name = name;
    this.price = price;
    this.size = size;
}

// 커피의 가격을 사이즈에 따라 계산하는 메서드
// 생성자함수.prototype.메서드명 = function(){} : 생성자 함수의 프로토타입에 메서드를 생성한다.
OrderCoffee.prototype.getPrice = function () {
    if (this.size === 'large') {
        return this.price + 500; // 큰 사이즈는 추가 가격이 붙습니다.
    }
    return this.price;
};

// 주문 접수 메서드
OrderCoffee.prototype.makeCoffee = function () {
    return `Making a ${this.size} ${this.name}}.`;
};

// 계산 메서드
OrderCoffee.prototype.order = function () {
    return `${this.name}} ${this.size} 는 ${this.price}원 입니다.`;
};

// 생성자 함수를 사용하여 각각의 커피 객체를 생성
var americano = new OrderCoffee('americano', 3000, 'small');
var latte = new OrderCoffee('Latte', 4000, 'medium');
var cappuccino = new OrderCoffee('Cappuccino', 4500, 'large');

// 메서드 호출
console.log(cappuccino.getPrice()); // 5000
console.log(americano.makeCoffee()); // "Making a small americano."
console.log(latte.order()); // "Latte medium 는 4000원 입니다."

- 문제 상황

커피숍에서는 여러 종류의 커피를 판매합니다. 각 커피는 이름, 가격, 사이즈, 재료 등의 속성을 가지고 있고, 커피를 만들고, 가격을 계산하는 등의 기능이 필요합니다. 만약 클래스를 사용하지 않는다면, 각 커피를 개별적으로 변수로 선언하고, 관련 함수를 따로 구현해야 합니다. 이는 중복된 코드가 많아지고, 관리하기 어려워질 수 있습니다.

- 클래스를 사용한 해결 방법

커피를 클래스로 정의함으로써, 각 커피 객체의 속성과 기능을 캡슐화하고 재사용할 수 있게 됩니다. 이를 통해 코드의 구조화, 조직화가 이루어지고, 새로운 커피 종류를 추가하거나 변경하기 쉬워집니다.

// 클래스를 사용한 경우
class OrderCoffee {
    // 생성자 함수 역할을 하는 constructor 메서드
    constructor(name, price, size) {
        this.name = name;
        this.price = price;
        this.size = size;
    }

    // 커피를 만드는 메서드
    makeCoffee() {
        return `Making a ${this.size} ${this.name}.`;
    }

    // 커피의 가격을 사이즈에 따라 계산하는 메서드
    getPrice() {
        if (this.size === 'large') {
            return this.price + 500; // 큰 사이즈는 추가 가격이 붙습니다.
        } else if (this.size === 'small') {
            return this.price - 500; // 작은 사이즈는 할인 가격이 적용됩니다.
        } else {
            return this.price;
        }
    }

    // 계산 메서드
    order() {
        return `${this.name} ${this.size} 는 ${this.getPrice()}원 입니다.`;
    }
}

// 클래스를 사용하여 각각의 커피 객체를 생성
const americano = new OrderCoffee('americano', 3000, 'small');
const latte = new OrderCoffee('Latte', 4000, 'medium');
const cappuccino = new OrderCoffee('Cappuccino', 4500, 'large');

// 메서드 호출
console.log(cappuccino.getPrice()); // 5000
console.log(americano.makeCoffee()); // "Making a small americano."
console.log(latte.order()); // "Latte medium 는 4000원 입니다."

1. 생성자 함수란?

클래스 함수를 사용하기 전에 생성자 함수에 대해 알아보겠습니다.

  • 객체를 생성할 때 속성을 초기화하고, 메서드를 추가할 수 있도록 하는 함수입니다.
  • 생성자 함수란 new 연산자를 통해 객체를 생성하는 함수를 말한다.
  • 생성자 함수는 일반 함수와 구분하기 위해 첫 글자를 대문자로 표기한다.

- 생성자 함수를 통해 객체 생성

// const animals = ['dog', 'cat', 'lion', 'tiger']; // 배열 리터럴

const animals = new Array('dog', 'cat', 'lion', 'tiger');
// 생성자 함수는 new 연산자를 통해 객체를 생성하는 함수를 말한다.

console.log(animals); // 배열 전체
console.log(animals.length); // 배열의 길이
console.log(animals[0]); // 배열의 첫번째 요소
console.log(animals.includes('dog')); // includes : 배열에 특정 요소가 있는지 확인
console.log(animals.includes('bird'));
// 위와 같이 length, includes등을 프로토타입 속성이라고 한다.

2. prototype 이란?

프로토타입 객체는 새로운 객체가 생성되기 위한 원형이 되는 객체이다.

array mdn 검색 -> 아래 링크를 보면 array에 prototype이 붙어있다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array

array에 사용할 수 있는 속성 및 메소드는 프로토타입에 연결되어 있다.

우리 모두는 부모님으로부터 여러 가지 특성(키, 눈 색깔 등)을 '상속'받습니다. 우리가 직접 이런 특성을 선택하거나 만들어내지 않았지만, 자연스럽게 이런 특성을 갖게 됩니다. JavaScript의 객체도 비슷하게 동작합니다. 각각의 객체는 특정한 프로토타입(부모 객체라고 생각할 수 있음)과 연결되어 있으며, 그 프로토타입의 속성이나 메서드를 마치 자신의 것처럼 사용할 수 있습니다.

- prototype을 이용하여 메소드 생성

  • length, includes등의 메소드는 이미 자바스크립트에서 만들어져 있는 속성이나 메소드이고
  • 이러한 속성이나 메소드를 사용자가 직접 만들어서 사용할 수 있다.
// new Array() 생성자 함수를 통해 배열을 생성한다.
const starbucks = new Array('아메리카노', '라떼', '카푸치노');

// Array.prototype.Menu() : 배열의 프로토타입에 Menu() 메소드를 생성한다.
Array.prototype.Menu = function () {
    console.log(this); // this : 배열을 가리킨다.
};
// starbucks의 배열에서 Menu() 메소드를 호출해 사용한다.
starbucks.Menu(); // ['아메리카노', '라떼', '카푸치노']

// 생성된 Menu() 메소드는 다른 배열에서도 사용할 수 있다.
const mega = ['메가리카노', '메가떼', '메가치노']; // 배열 생성
mega.Menu(); // ['메가리카노', '메가떼', '메가치노']

3. prototype을 이용한 메소드 재활용

- 메소드 중복 사용

const americano = {
    name: '아메리카노',
    price: 3000,
    orderCoffee: function () {
        // 일반 함수에서 this는 호출되는 객체를 가리킨다.
        return `${this.name}는 ${this.price}원 입니다.`; // this : winter 객체를 가리킨다.
    },
};

const latte = {
    name: '라떼',
    price: 4000,
    orderCoffee: function () {
        return `${this.name}는 ${this.price}원 입니다.`;
    },
};

console.log(americano.orderCoffee()); // 아메리카노는 3000원 입니다.
console.latte(fall.orderCoffee()); // 라떼는 4000원 입니다.
// 같은 로직의 코드가 반복되어 비효율적이다.

- call을 이용한 메소드 재활용

const americano = {
    name: '아메리카노',
    price: 3000,
    orderCoffee: function () {
        return `${this.name}는 ${this.price}원 입니다.`;
    },
};

const latte = {
    name: '라떼',
    price: 4000,
};

console.log(americano.orderCoffee()); // 아메리카노는 3000원 입니다.
console.log(americano.orderCoffee.call(latte)); // 라떼는 4000원 입니다.

- prototype을 이용한 메소드 재활용

  • 위의 코드에서 prototype을 이용하면 조금 더 효율적으로 코드를 작성할 수 있다.
// 위에서 객체로 생성한 코드를 OrderCoffee 생성자 함수로 만든다.
function OrderCoffee(name, price) {
    // 함수 내부에 this 키워드를 사용하여 객체의 속성을 생성할 수 있다.
    this.name = name; // this.name 속성에 name 매개변수를 할당한다.
    this.price = price;
}

// OrderCoffee 생성자 함수의 프로토타입에 order 메소드를 생성한다.
// 생성자 함수에서 화살표 함수 사용시 this가 window를 가리키기 때문에 사용할 수 없다.
OrderCoffee.prototype.order = function () {
    return `${this.name}는 ${this.price}원 입니다.`;
    // this : OrderCoffee 생성자 함수를 가리킨다.
};

// OrderCoffee 생성자 함수를 통해 객체를 생성한다.
// 생성자 함수란 new 연산자를 통해 객체를 생성하는 함수를 말한다.
// 생성자 함수를 호출할 때 new를 붙여서 호출한다.
const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);

console.log(americano); // OrderCoffee {name: "아메리카노", price: "3000"}
console.log(latte); //  OrderCoffee {name: "라떼", price: "4000"}
console.log(americano.order()); // 아메리카노는 3000원 입니다.
console.log(latte.order()); // 라떼는 4000원 입니다.

4. es6 class 이용한 메소드 재활용

  • es6에서는 class 문법을 사용하여 객체를 생성할 수 있다.
  • class 문법을 사용하면 생성자 함수를 사용하지 않고도 객체를 생성할 수 있다.
  • class 문법에서는 constructor() 메소드를 사용하여 객체를 생성하고 프로토타입에 메소드를 생성한다.

- class 문법 사용 방법

// OrderCoffee 클래스를 생성한다.
class OrderCoffee {
    // constructor : 생성자 함수 역할을 해 객체를 생성한다.
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
    // OrderCoffee 클래스의 프로토타입에 order() 메소드를 생성한다.
    // 따움표로 구분하지 않는다.
    order() {
        return `${this.name}는 ${this.price}원 입니다.`;
    }

    // making
    making() {
        return `${this.name}를 만드는 중...`;
    }

    // made
    made() {
        return `${this.name}가 만들어졌습니다.`;
    }
}

// OrderCoffee 생성자 함수를 통해 객체를 생성한다.
const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);

console.log(americano.order()); // 아메리카노는 3000원 입니다.
console.log(americano.making()); // 아메리카노를 만드는 중...
console.log(americano.made()); // 아메리카노가 만들어졌습니다.

console.log(latte.order()); // 라떼는 4000원 입니다.
console.log(latte.making()); // 라떼를 만드는 중...
console.log(latte.made()); // 라떼가 만들어졌습니다.

5. class 상속

- 클래스 상속의 개념

클래스 상속은 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 사용하는 것입니다. 이를 통해 코드 재사용성을 높이고, 유지보수를 쉽게 할 수 있습니다.

예시로 이해하기

  • 기본 클래스 : Order

먼저, 스타벅스의 기본 커피 정보를 담는 클래스를 생각해봅시다.

class OrderCoffee {
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
}
  • 상속받는 클래스: Special

특별한 커피는 기본 커피의 정보를 가지고 있지만, 추가 정보도 필요합니다. 이를 위해 OrderCoffee 클래스를 상속받아 Special 클래스를 만들 수 있습니다.

class Special extends OrderCoffee {
    constructor(name, price, specialFeature) {
        super(name, price); // 부모 클래스의 생성자를 호출
        this.specialFeature = specialFeature;
    }
}
  • 간단한 설명

    • OrderCoffee는 기본 커피의 이름과 가격을 저장하는 클래스입니다.
    • Special은 OrderCoffee를 상속받아 특별한 기능(specialFeature)을 추가한 클래스입니다.
    • extends 키워드를 사용하여 상속받으며, super()를 통해 부모 클래스의 생성자를 호출합니다.

- class 상속 사용 방법

super 키워드를 사용하여 부모 클래스의 생성자를 호출한다.

class OrderCoffee {
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
    calling() {
        return `${this.name}는 ${this.price}원 입니다.`;
    }
    // making
    making() {
        return `${this.name}를 만드는 중...`;
    }

    // made
    made() {
        return `${this.name}가 만들어졌습니다.`;
    }
}

class SpecialCoffee extends OrderCoffee {
    constructor(name, price, size) {
        super(name, price); // super 키워드를 사용하여 부모 클래스의 생성자를 호출한다.
        this.size = size; // size 속성을 추가한다.
    }

    order() {
        return `스페셜 커피 ${this.name} ${this.size} 사이즈는 ${this.price}입니다.`;
    }
}

const americano = new OrderCoffee('아메리카노', 3000);
const latte = new OrderCoffee('라떼', 4000);

console.log(americano.calling()); // 아메리카노는 3000원 입니다.
console.log(latte.calling()); // 라떼는 4000원 입니다.

const americanoS = new SpecialCoffee('아인슈페너', 6000, 'tall');
const latteS = new SpecialCoffee('연유라떼', 5000, 'small');

console.log(americanoS.order()); // 스페셜 커피 아인슈페너 tall 사이즈는 6000입니다.
console.log(latteS.order()); // 스페셜 커피 연유라떼 small 사이즈는 5000입니다.

console.log(americanoS.making()); // 아인슈페너를 만드는 중...
console.log(americanoS.made()); // 아인슈페너가 만들어졌습니다.

7. 리액트에서 클래스 사용하기

클래스 컴포넌트는 복잡한 상태 관리와 생명주기 메서드를 활용할 때 유리하며, 특히 기존의 큰 규모 프로젝트 유지보수나 특정 라이브러리 요구사항에 따라 필요할 수 있습니다. 그러나 리액트는 최신 기능과 성능 개선을 위해 함수형 컴포넌트 사용을 권장하고 있습니다.

- 클래스 함수 사용

// src/OrderCoffee.js
import React from 'react';

class OrderCoffee extends React.Component {
    constructor(props) {
        // 생성자 함수에서 super(props)를 호출하여 부모 클래스의 생성자를 호출한다.
        super(props);
        // state 객체를 사용하여 컴포넌트의 상태를 관리한다.
        this.state = {
            name: props.name,
            price: props.price,
        };
    }

    calling() {
        return `${this.state.name}는 ${this.state.price}원 입니다.`;
    }

    making() {
        return `${this.state.name}를 만드는 중...`;
    }

    made() {
        return `${this.state.name}가 만들어졌습니다.`;
    }

    render() {
        return (
            <div>
                <h1>{this.calling()}</h1>
                <p>{this.making()}</p>
                <p>{this.made()}</p>
            </div>
        );
    }
}

export class SpecialCoffee extends OrderCoffee {
    constructor(props) {
        super(props);
        this.state = {
            ...this.state, // 부모 클래스의 state를 상속받는다.
            size: props.size, // size 속성을 추가한다.
        };
    }

    order() {
        return `스페셜 커피 ${this.state.name} ${this.state.size} 사이즈는 ${this.state.price}입니다.`;
    }

    render() {
        return (
            <div>
                <h1>{this.order()}</h1>
                <h2>{super.calling()}</h2>
                <p>{this.making()}</p>
                <p>{this.made()}</p>
            </div>
        );
    }
}

export default OrderCoffee;
// src/App.js
import React from 'react';
import OrderCoffee, { SpecialCoffee } from './components/views/coffee/OrderCoffee';

function App() {
    return (
        <div>
            <OrderCoffee
                name="아메리카노"
                price={3000}
            />
            <OrderCoffee
                name="라떼"
                price={4000}
            />
            <SpecialCoffee
                name="아인슈페너"
                price={6000}
                size="tall"
            />
            <SpecialCoffee
                name="연유라떼"
                price={5000}
                size="small"
            />
        </div>
    );
}

export default App;

- 함수형 컴포넌트 사용

// src/OrderCoffee.js
import React from 'react';

function OrderCoffee({ name, price }) {
    const calling = () => `${name}는 ${price}원 입니다.`;
    const making = () => `${name}를 만드는 중...`;
    const made = () => `${name}가 만들어졌습니다.`;

    return (
        <div>
            <h1>{calling()}</h1>
            <p>{making()}</p>
            <p>{made()}</p>
        </div>
    );
}

export function SpecialCoffee({ name, price, size }) {
    const order = () => `스페셜 커피 ${name} ${size} 사이즈는 ${price}입니다.`;

    return (
        <div>
            <h1>{order()}</h1>
            <h2>{calling()}</h2>
            <p>{making()}</p>
            <p>{made()}</p>
        </div>
    );
}

export default OrderCoffee;
// src/App.js
import React from 'react';
import OrderCoffee, { SpecialCoffee } from './components/views/coffee/OrderCoffee';

function App() {
    return (
        <div>
            <OrderCoffee
                name="아메리카노"
                price={3000}
            />
            <OrderCoffee
                name="라떼"
                price={4000}
            />
            <SpecialCoffee
                name="아인슈페너"
                price={6000}
                size="tall"
            />
            <SpecialCoffee
                name="연유라떼"
                price={5000}
                size="small"
            />
        </div>
    );
}

export default App;

- 클래스 컴포넌트와 함수형 컴포넌트 비교

  • 클래스 컴포넌트는 복잡한 상태 관리와 생명주기 메서드를 활용할 때 유리하다.
  • 함수형 컴포넌트는 간단한 컴포넌트를 만들 때 유리하며, 최신 기능과 성능 개선을 위해 권장된다.
티스토리 친구하기